MaybeUninit from mutable reference

Hello,

How can I convert a &mut T to an &mut MaybeUninit<T> without copying any data?
Is it sound to call transmute<&mut T, &mut MaybeUninit<T>>(x), or is there a better solution?

Thanks in advance,
Tage

Why do you need this? Even if you do this, you can't write uninitialized memory to the reference, because it actually points to an initialized T.

1 Like

Hmmm. That's a tricky question.

First, let's write a wrapper function to ensure that the lifetimes are constrained to be equal:

unsafe fn as_maybe_uninit_mut<T>(x: &mut T) -> &mut MaybeUninit<T> {
    mem::transmute(x)
}

This fn must be unsafe because otherwise you could do this in safe code:

let mut vec = vec![6];
*as_maybe_uninit_mut(&mut vec) = MaybeUninit::uninit();
drop(vec);

This is clearly UB, because an uninitialized Vec is dropped. But the following case is cloudier:

let mut vec = vec![6];
unsafe {
    let r = as_maybe_uninit_mut(&mut vec);

    // make `vec` temporarily uninitialized
    // (being careful not to do anything that could unwind)
    let old = r.as_ptr().read();
    *r = MaybeUninit::uninit(); 

    // put the vec back
    r.as_mut_ptr().write(old);
}

In this example, vec briefly is uninitialized. To the best of my current understanding, this is unfortunately still UB even if we don't touch vec at all while it is uninitialized (and this is part of the reason why MaybeUninit<T> was introduced), simply because vec is in scope. (a brief phrasing of the condition for soundness here might be: Any value named by a local must be initialized at all times that the local is in scope)

If my understanding is correct, then as_maybe_uninit_mut is useless, as its only meaningful use case (assignment of uninitialized data) causes UB.

1 Like

Why do you need this? Even if you do this, you can't write uninitialized memory to the reference, because it actually points to an initialized T.

I want to use the replace_with crate, that crate has the following functions:

  •   dest: &mut T, 
      default: D, 
      f: F
    

)```
Temporarily takes ownership of a value at a mutable location, and replace it with a new value based on the old one. Replaces with default on panic.

  • pub fn replace_with_or_abort<T, F: FnOnce(T) -> T>(dest: &mut T, f: F)
    Temporarily takes ownership of a value at a mutable location, and replace it with a new value based on the old one. Aborts on panic.
  • Some more.

I want to use any of these functions in a generic function in a library. I don't want to use replace_with because I don't want to force the user to provide a default function every time. I do not either want to use replace_with_or_abort, since then there will be no helpful panic information.

But I thought that if I put in a &mut MaybeUninit<T> in replace_with instead of &mut T, then I can use MaybeUninit::uninit() as default if it panics. But the problem is just that I need to convert from &mut T to &mut MaybeUninit<T> and I need to know that it is sound.

Nope, this is not sound, as both @ExpHP and I have said. The replace_with crate provides the only safe ways of doing this (ignoring Copy types)

Okay, so this actually falls under my first example (i.e. this is very clearly UB). Because the original value is of type T (and not MaybeUninit<T>), it will still be dropped when we unwind past the stack frame that owns it, and hence you will be dropping uninitialized data.

(for instance, if the user used this with a Vec<i32> and their closure panicked, the generated code would most likely drop the Vec twice; once as we unwind back out through the user-provided closure, and again as we unwind past where the Vec lives)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.