-
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
There should be a way to unwrap MaybeUninits without moving them #61011
Comments
That would be rust-lang/rfcs#2645. But yes, the intention is that you can do such transmutes. And indeed the section at https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout kind of says that you may do that, doesn't it? I guess it could be more explicit. PRs welcome! Interaction of |
Yeah, that's good, but it's unclear what kinda of transmutes are allowed when you're sure it's definitely initted. Examples would be good (I might add some). The Arc case is where you've initialized the Arc with uninit contents and are the sole owner. Once you've filled in the contents (maybe via ffi!) you should be able to turn it into a "normal" arc. |
This also becomes important for FFI: the reason we made the repr(transparent) RFC in the first place is that There's going to be a similar situation here with FFI providing or working on uninitialized places. |
Yeah, the aforementioned RFC was opened because of by-value-FFI concerns. But otherwise it shouldn't matter. Also we had an astute lack of examples for passing uninitialized data by value. |
Why does it have to be by value? FFI can pass me a reference to a complicated C++ datastructure with some uninit stuff in it, I have to be able to operate on that safely. |
Speaking solely as an FFI author, I believe the Since In order to implement this in the library rather than in the language, I would advocate that impl<T> MaybeUninit<T> {
pub unsafe fn from_pointer(ptr: *mut T) -> Option<&mut Self> {
if ptr.is_null() || !ptr.is_aligned::<T>() {
return None;
}
Some(&mut * (ptr as *mut Self))
}
} I believe this would address both my wants as an FFI author, and Manish's concerns as a Rust-internal author, by removing Pure-Rust usage of this might be to produce For cases like Manish described, where the allocation of a container happens separately enough from the initialization of its contents that the allocated and initialized container of uninitialized data must cross function boundaries before its interior is initialized, I would expect that such transformations would have to be methods on the container, rather than on impl<T> Container<T> {
pub fn deferred_init(uninit: Container<MaybeUninit<T>>, value: T) -> Self {
uninit.write(value);
unsafe {
let disasm = uninit.raw_parts();
Self::from_raw_parts(disasm)
}
}
} would serve to remove the Is this a useful summary of the various concerns about deferred and/or out-of-place initialization, and how they might be addressed? Edit: propagate the |
The only additional effect |
That is not a safe function. But I don't understand what that has to do with the discussion here...? |
Not necessarily, it means that |
That, too, is ensured by |
I imagined we'd have something like this for impl Box<MaybeUninit<T>> { // not sure if this works but you get the idea
pub fn uninit() -> Self { Box::new(MaybeUninit::new()) } // with some hack to avoid a stack allocation
// also: zeroed, new
unsafe pub fn assume_init(self) -> Box<T> { mem::transmute(self) }
pub fn write(&mut self, value: T) -> &mut Box<T> {
MaybeUninit::write(&mut *self, value);
unsafe { mem::transmute(self) }
}
} Though I am still not convinced the return value of |
It doesn't address my concerns, since my concerns are all about MaybeUninit's semantics when embedded deeply inside some other structure, and a need to be able to get rid of it. Things that can be written as a library don't matter to me: I can write such a function myself. My question is more about the fundamental boundaries of the UB surrounding this.
Yes, but it's not clear that all such transmutes are safe (provided the Figuring this out is what this issue is about. If such transmutes are always safe, then that's good, we should document that, and we should add examples doing it. |
At this point I should also quote what @rkruppe said on Zulip:
So basically the thinking is that such transmutes are safe only when the type constructors involved ignore the niche. I think we should be able to guarantee that arrays and tuples do ignore the niche. |
Ah, that's a good point. I don't know how to document this well. (Yes, I know this is mentioned on the layout side, but layout being the same is only a prerequisite to transmutes being safe) |
This seems like it'd want a marker trait to denote that types without niches are sound to transform |
@myrrlyn that only solves half the problem. |
It's fine having APi for Can this work? It cannot be expressed with language terms, but maybe some compiler builtin? |
IMO we can do that by saying they are structural product types. |
I would just like to note that many of the potential use-cases of "MaybeUninit, but it goes away" for pure rust applications are just The Placer API, and I think it would be best to pursue those applications through that avenue. |
It looks like you can just transmute from |
There's even an unstable function which you can use instead of the transmute: https://doc.rust-lang.org/nightly/std/boxed/struct.Box.html#method.assume_init |
I'd really like having something like: fn unwrap_transparent<T<_>, R: ReprTransparent>(value: T<R<_>>) -> T<_> {
unsafe { mem::transmute(value) }
}
fn unwrap_uninit<T<_>>(value: T<MaybeUninit<_>>) -> T<_> {
unwrap_transparent(value);
} Or even more generic like |
I started a discussion about strong updates (a possible solution to this issue): https://internals.rust-lang.org/t/strong-updates-status/21074. As noted by Ralf, exclusive access is needed, so it won't be possible to remove a MaybeUninit from an Arc. The Arc content needs to be exclusively owned for that (either proved using unsafe or by using an intermediate UniqueArc or BoxArc type). As noted by Gankra, the placer API would solve most of the use-cases. But I don't think it will solve all. I'm thinking about type-states: impl Door<Closed> {
fn old_open(self) -> Door<Open>;
fn new_open(self: &mut [Door<Closed> .. Door<Open>]);
} One can implement |
Type-changing mutable ref is a significant language extension, please discuss that in separate thread(s). :)
|
Yes, that's the first sentence of my message :-) a link to an existing discussion for those looking for possible solutions (since the placer API seems dead AFAICT). |
MaybeUninit
has an API where you construct aMaybeUninit<T>
, can operate on it via raw pointer methods, and then can callassume_init()
to move the inner value out. It also has methods that let you assume that it is initialized and obtain safe Rust references.This is great for temporary initialization on the stack, but this doesn't work so well for the heap. What if you need to gradually initialize
Vec<T>
orArc<T>
heap values? This ends up leaving a carcass of aMaybeUninit
in the type forever, basically infecting the type with an unsafe abstraction from the time of its initialization.One solution here is to make sure
MaybeUninit
andManuallyDrop
have a repr ofT
(basically,repr(transparent)
, except we haven't defined that for unions) so that you can always transmute&MaybeUninit<T>
to&T
, orArc<MaybeUninit<T>>
intoArc<T>
.The fact that
MaybeUninit
has methods specifically for doing this kind of thing with slices seems to point to the need for this ability to be generalized.Either way we should have clear documentation for using
MaybeUninit
with values not on your stack (on the heap, or passed in as a reference). Ideally we allow you to transmute it in such cases (and have examples doing this), but if we can't, we should still document that you shouldn't use it that way. (I think that this choice would preclude a lot of legitimate and necessary use cases, especially in FFI)cc @eddyb @RalfJung
The text was updated successfully, but these errors were encountered: