rustmaybeuninit

Safely treating a `&[T]` as a `&[MaybeUninit<T>]` in Rust


How can/should I obtain a &[MaybeUninit<T>] from a &[T]?

Intuitively, I should be allowed to treat initialized memory as being possibly uninitialized, but intuition and unsafe do not always mix well... Since MaybeUninit<T> is guaranteed to have the same size and alignment as T, it should be safe to transmute a &[T] into a &[MaybeUninit<T>]?

Then again, the current implementation of MaybeUninit::new(val) does more than just transmuting, it adds a ManuallyDrop::new(val) wrapper. Does this mean that if I implemented slice_as_maybeuninit via transmuting, I'd run into problems with destructors not being run? Which is not unsafe, but still undesirable enough to restrict such a function to slices of data that implement Copy (i.e., which do not implement Drop).

To make my question(s) more precise:


Solution

  • How should I implement fn slice_as_maybeuninit<'a, T>(s: &'a [T]) -> &'a [MaybeUninit<T>], if at all?

    Not using transmute (I'm unsure if this is unsound because layout of slices is not guaranteed, but it's definitely not good practice). However, a simple cast will do:

    fn slice_as_maybeuninit<'a, T>(s: &'a [T]) -> &'a [MaybeUninit<T>] {
        // SAFETY:
        //  - `MaybeUninit<T>` is guaranteed to have the same layout as `T`.
        //  - Slices with compatible elements layout have compatible layout,
        //    since slices are have the same layout as the backing array
        //    and array lay all elements consecutively.
        //  - It is always safe to treat initialized values as possibly-initialized.
        unsafe { &*(s as *const [T] as *const [MaybeUninit<T>]) }
    }
    

    bytemuck can also help here, if you can implement the NoUninit and AnyBitPattern traits for T. Just use must_cast_slice().

    An important note, however, is that it is not sound to implement &mut [T] -> &mut [MaybeUninit<T>] (or &UnsafeCell<[T]> -> &UnsafeCell<[MaybeUninit<T>]>, or with any other interior mutability container) conversion, since that will allow the user to perform the conversion, put MaybeUninit::uninit() here, then read it as initialized from the original reference.

    How should I implement fn slice_as_maybeuninit<'a, T: Copy>(s: &'a [T]) -> &'a [MaybeUninit<T>]? // note the T: Copy bound

    By calling into the previous function, I guess?

    Bonus: Why does std::mem::MaybeUninit not provide these operations for me?

    It doesn't provide a lot of possible operations. You can suggest it, or maybe Project Safe Transmute will eventually make this possible.