-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking Issue for MaybeUninit methods for arrays #96097
Comments
…up, r=dtolnay MaybeUninit array cleanup * Links `MaybeUninit::uninit_array` to meta-tracking issue * Links `MaybeUninit::array_assume_init` to meta-tracking issue * Unstably constifies `MaybeUninit::array_assume_init` Another thing worth mentioning: this splits the const feature flag for `maybe_uninit_uninit_array` into `const_maybe_uninit_uninit_array` to avoid weird cases where only one gets stabilised. Note that it may be desired to keep the `array_assume_init` method linked to its dedicated issue, but at least for now, I decided to link to the meta-tracking issue so that all of the methods lead users to the same place. But I can revert that bit if desired. The meta-tracking issue that I filed is rust-lang#96097.
I'm a bit concerned about the size and inconsistency of this API, as the corresponding functions for single values are all methods, while for arrays and slices we have associated functions. For example, I would rather prefer a trait-based API like this: unsafe trait Uninitialized {
type Initialized: ?Sized;
fn uninit() -> Self
where Self: Sized;
fn zeroed() -> Self
where Self: Sized;
unsafe fn assume_init(self) -> Self::Initialized
where Self: Sized;
unsafe fn assume_init_ref(&self) -> &Self::Initialized;
...
}
unsafe impl<T> Uninitialized for MaybeUninit {...}
unsafe impl<T, const N: usize> Uninitialized for [T; N] {...}
unsafe impl<T> Uninitialized for [T] {...} or with a marker trait like I proposed here. |
I don't think it's worth making a trait for this. I've had many use cases for uninit_array before, and none for a generic solution. MaybeUninit is often used with arrays, since that's where initialization tends to be the most expensive. While it is a trivial function to write yourself, I do think that it's worth it to stabilize |
Another thing worth asking: why not have methods directly implemented on arrays? You'd still need some form of This seems possible specifically because the types exist in core, but open to other interpretations. I also suggested perhaps just adding Of course, the weirdness of |
@clarfonthey with the current implementation, |
Docs patch extracted from #102023: Subject: [PATCH] Add MaybeUninit array transpose impls
Signed-off-by: Alex Saveau <[email protected]>
---
Index: library/core/src/mem/maybe_uninit.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs
--- a/library/core/src/mem/maybe_uninit.rs (revision 8147e6e427a1b3c4aedcd9fd85bd457888f80972)
+++ b/library/core/src/mem/maybe_uninit.rs (date 1665873934720)
@@ -117,15 +117,12 @@
/// `MaybeUninit<T>` can be used to initialize a large array element-by-element:
///
/// ```
-/// use std::mem::{self, MaybeUninit};
+/// use std::mem::MaybeUninit;
///
/// let data = {
-/// // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
-/// // safe because the type we are claiming to have initialized here is a
-/// // bunch of `MaybeUninit`s, which do not require initialization.
-/// let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
-/// MaybeUninit::uninit().assume_init()
-/// };
+/// // Create an uninitialized array of `MaybeUninit`.
+/// let mut data: [MaybeUninit<Vec<u32>>; 1000] = MaybeUninit::uninit().transpose();
///
/// // Dropping a `MaybeUninit` does nothing, so if there is a panic during this loop,
/// // we have a memory leak, but there is no memory safety issue.
@@ -133,25 +130,23 @@
/// elem.write(vec![42]);
/// }
///
-/// // Everything is initialized. Transmute the array to the
-/// // initialized type.
-/// unsafe { mem::transmute::<_, [Vec<u32>; 1000]>(data) }
+/// // Everything is initialized. Convert the array to the initialized type.
+/// unsafe { MaybeUninit::<[Vec<_>; 1000]>::assume_init(data.transpose()) }
/// };
///
-/// assert_eq!(&data[0], &[42]);
+/// assert_eq!(&data[42], &[42]);
/// ```
///
/// You can also work with partially initialized arrays, which could
/// be found in low-level datastructures.
///
/// ```
/// use std::mem::MaybeUninit;
/// use std::ptr;
///
-/// // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
-/// // safe because the type we are claiming to have initialized here is a
-/// // bunch of `MaybeUninit`s, which do not require initialization.
-/// let mut data: [MaybeUninit<String>; 1000] = unsafe { MaybeUninit::uninit().assume_init() };
+/// // Create an uninitialized array of `MaybeUninit`.
+/// let mut data: [MaybeUninit<String>; 1000] = MaybeUninit::uninit().transpose();
/// // Count the number of elements we have assigned.
/// let mut data_len: usize = 0;
/// |
…r=scottmcm Add MaybeUninit array transpose From impls See discussion in rust-lang#101179 and rust-lang#96097. I believe this solution offers the simplest implementation with minimal future API regret. `@RalfJung` mind doing a correctness review?
Add MaybeUninit array transpose From impls See discussion in rust-lang/rust#101179 and rust-lang/rust#96097. I believe this solution offers the simplest implementation with minimal future API regret. `@RalfJung` mind doing a correctness review?
I’d like to argue for: yes. Stabilizing now can allow users to remove some
New proposals can be made at any time. The methods already in Nightly don’t need to be blocked on exhausting the design space for other thematically-related methods. |
What's the rationale behind separate feature flag for const? Is there any reason that, if we stabilise this function, we want it to be non-const? I think we should merge the feature flags and stabilise it in one go. |
I don't like name of this method. |
That ship has already sailed with Option::transpose and Result::transpose. In this case, I feel it's somewhat unlikely that people doing high-level matrix operations would need to use the low-level Here's the original discussion for |
Inline const expressions are said to render this method less necessary, but such expressions aren't even necessary for use std::mem::{MaybeUninit, transmute};
fn main() {
let mut arr = [MaybeUninit::uninit(); 4];
for i in 0..arr.len() {
unsafe { *arr[i].as_mut_ptr() = i as u8; }
}
// Prints "[0, 1, 2, 3]"
println!("{:?}", unsafe { transmute::<_, [u8; 4]>(arr) });
} What's then the point of this method for these cases? Does it have any codegen advantage? I checked on godbolt.org and the generated assembly for this code didn't initialize the array elements. (Edit: even after tweaking this code to initialize the array positions to Edit: given that for non- Edit 2: actually, it looks like even for non- |
Because it labels the action you're intending to do, without the reader having to puzzle out "oh, this time the transmute was to initialize the data within the type system". Exactly like f32::to_bits and Option::unwrap and dozens of other small helper functions. Putting the name on the action is the value, not because there's better codegen than you could write yourself. |
Thanks - I should clarify that I was referring to |
Ah, with |
Another thing which is arguably inconsequential is that the intent is a bit more clear: even though I know that the compiler isn't really copying the same uninitialised data N times into the array, it feels better to explicitly say "I want an uninitialised array" rather than "I want to initialise an array with each element uninitialised." This is actually a reason why I argue that |
I believe |
Dang, that's disappointing. |
I'm glad you posted this, it's much nicer than the |
I agree that it is possible to obtain an uninitialized array other ways, but |
I am interested in Would it be considered related to this issue? |
@rust-lang/libs-api: (Only in regard to I propose that we accept #125082 to remove When fn workaround<T, const N: usize>() -> [MaybeUninit<T>; N] {
trait Workaround: Sized {
const UNINIT: MaybeUninit<Self>;
}
impl<T> Workaround for T {
const UNINIT: MaybeUninit<Self> = MaybeUninit::uninit();
}
[<T as Workaround>::UNINIT; N]
} These days, with My opinion aligns with @kpreid's characterization in the PR description:
The counter perspective is the one in #125082 (comment).
I do not dispute that it is 25% fewer characters. For the common case of copyable contents like an uninit u8 buffer, Regarding readability, the comparison assumes one has read and retained this part of https://doc.rust-lang.org/std/mem/union.MaybeUninit.html, which is not free. Superseding a part of the extensive, ad-hoc API of For discoverability, I'd expect |
@rfcbot fcp close |
Team member @dtolnay has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
I realized I'll need to file a compiler diagnostics bug. Currently on nightly: use std::mem::MaybeUninit;
fn main() {
let _: [MaybeUninit<String>; 2] = [MaybeUninit::uninit(); 2];
} error[E0277]: the trait bound `String: Copy` is not satisfied
--> src/main.rs:4:40
|
4 | let _: [MaybeUninit<String>; 2] = [MaybeUninit::uninit(); 2];
| ^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `String`, which is required by `MaybeUninit<String>: Copy`
|
= note: required for `MaybeUninit<String>` to implement `Copy`
= note: the `Copy` trait is required because this value will be copied for each element of the array
= help: create an inline `const` block, see RFC #2920 <https://github.com/rust-lang/rfcs/pull/2920> for more information
help: consider creating a new `const` item and initializing it with the result of the function call to be used in the repeat position
|
4 ~ const ARRAY_REPEAT_VALUE: MaybeUninit<String> = MaybeUninit::uninit();
5 ~ let _: [MaybeUninit<String>; 2] = [ARRAY_REPEAT_VALUE; 2];
| We should change this to suggest const ARRAY_REPEAT_VALUE: MaybeUninit<String> = MaybeUninit::uninit();
let _: [MaybeUninit<String>; 2] = [ARRAY_REPEAT_VALUE; 2]; |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
Is the intention that a new tracking issue be opened for the remaining methods? |
A FCP to close doesn't mean that the issue has to be actually closed after it completes. dtolnay wrote
|
The final comment period, with a disposition to close, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. |
This should be closed since inline const blocks has already landed in stable right? Or is that only part of this issue? Just going thru MaybeUninit and seeing the new uninit array method is still on nightly |
See here.
|
This is a meta-tracking issue for multiple APIs that are linked across multiple issues. Right now it only includes two methods, but since there seems to be a desire to add more, this issue can be used as a placeholder for those discussions until those methods are added.
Public API
Steps / History
MaybeUninit
methodsuninit_array
,slice_get_ref
,slice_get_mut
#65580MaybeUninit
methodarray_assume_init
#80600Relevant Links
Unresolved Questions
MaybeUninit::uninit_array::<LEN>()
be stabilised if it can be replaced by[const { MaybeUninit::uninit() }; LEN]
?array_assume_init
the right pattern, or should we convert from[MaybeUninit<T>, N]
back toMaybeUninit<[T; N]>
first?The text was updated successfully, but these errors were encountered: