Question

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:

  • How should I implement fn slice_as_maybeuninit<'a, T>(s: &'a [T]) -> &'a [MaybeUninit<T>], if at all?
  • How should I implement fn slice_as_maybeuninit<'a, T: Copy>(s: &'a [T]) -> &'a [MaybeUninit<T>]? // note the T: Copy bound
  • Bonus: Why does std::mem::MaybeUninit not provide these operations for me?
 3  100  3
1 Jan 1970

Solution

 6

A transmutation from [T] to [MaybeUninit<T>] is completely safe. Docs on its layout:

MaybeUninit<T> is guaranteed to have the same size, alignment, and ABI as T:

You do however want to use from_raw_parts to construct the new slice instead of just transmute.

fn slice_as_maybeuninit<T>(s: &[T]) -> &[MaybeUninit<T>] {
    unsafe { std::slice::from_raw_parts(s.as_ptr() as *const MaybeUninit<T>, s.len()) }
}

[Will I] run into problems with destructors not being run?

No. In other circumstances you could, MaybeUninit relaxes the initialization criteria so it won't drop its contents since it alone doesn't know if its contents are initialized. However, you are only transmuting a &[T] which is only a reference to the contents; so it won't try to drop values anyway.

Copy is not necessary since no values of T are being created.

2024-07-02
kmdreko

Solution

 5

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.

2024-07-02
Chayim Friedman