Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it sound to transmute a MaybeUninit<[T; N]> to [MaybeUninit<T>; N]?

Tags:

rust

Is the following code sound?

#![feature(maybe_uninit)]
use std::mem;
const N: usize = 2; // or another number
type T = String; // or any other type

fn main() {
    unsafe {
        // create an uninitialized array
        let t: mem::MaybeUninit<[T; N]> = mem::MaybeUninit::uninitialized();
        // convert it to an array of uninitialized values
        let mut t: [mem::MaybeUninit<T>; N] = mem::transmute(t);
        // initialize the values
        t[0].set("Hi".to_string());
        t[1].set("there".to_string());
        // use the values
        println!("{} {}", t[0].get_ref(), t[1].get_ref());
        // drop the values
        mem::replace(&mut t[0], mem::MaybeUninit::uninitialized()).into_initialized();
        mem::replace(&mut t[1], mem::MaybeUninit::uninitialized()).into_initialized();
    }
}

I should note that miri runs it without problems.

like image 782
llogiq Avatar asked Mar 23 '19 11:03

llogiq


1 Answers

Correction: The answer below still holds in the general case, but in the case of MaybeUninit there are a few handy special cases about memory layout that makes this actually safe to do:

First, the documentation for MaybeUninit has a layout section stating that

MaybeUninit<T> is guaranteed to have the same size and alignment as T.

Secondly, the language reference says this about array layouts:

Arrays are laid out so that the nth element of the array is offset from the start of the array by n * the size of the type bytes. An array of [T; n] has a size of size_of::<T>() * n and the same alignment of T.

This means that the layout of MaybeUninit<[T; n]> and the layout of [MaybeUninit<T>; n] are the same.


Original answer:

From what I can tell, this is one of those things that are likely to work but not guaranteed, and may be subject to compiler-specific or platform-specific behavior.

MaybeUninit is defined as follows in the current source:

#[allow(missing_debug_implementations)]
#[unstable(feature = "maybe_uninit", issue = "53491")]
pub union MaybeUninit<T> {
    uninit: (),
    value: ManuallyDrop<T>,
}

Since it's not marked with the #[repr] attribute (as opposed to for instance ManuallyDrop), it's in the default representation, of which the reference says this:

Nominal types without a repr attribute have the default representation. Informally, this representation is also called the rust representation.

There are no guarantees of data layout made by this representation.

In order to transmute from Wrapper<[T]> to [Wrapper<T>], it must be the case that the memory layout of Wrapper<T> is exactly the same as the memory layout of T. This is the case for a number of wrappers, such as the previously mentioned ManuallyDrop, and those will usually be marked with the #[repr(transparent)] attribute.

But in this case, this is not necessarily true. Since () is a zero-size type, it's likely that the compiler will use the same memory layout for T and MaybeUninit<T> (and this is why it's working for you), but it is also possible that the compiler decides to use some other memory layout (e.g. for optimization purposes), in which case transmuting will not work anymore.


As a specific example, the compiler may chose to use the following memory layout for MaybeUninit<T>:

+---+---+...+---+
| T         | b |     where b is "is initialized" flag
+---+---+...+---+

According to the above quote, the compiler is allowed to do this. In this case, [MaybeUninit<T>] and MaybeUninit<[T]> have different memory layouts, since MaybeUninit<[T]> has one b for the entire array, while [MaybeUninit<T>] has one b for each MaybeUninit<T> in the array:

MaybeUninit<[T]>:
+---+...+---+---+...+---+...+---+...+---+---+
| T[0]      | T[1]      | … | T[n-1]    | b |
+---+...+---+---+...+---+...+---+...+---+---+
Total size: n * size_of::<T>() + 1

[MaybeUninit<T>]
+---+...+---+----+---+...+---+----+...+---+...+---+------+
| T[0]      |b[0]| T[1]      |b[1]| … | T[n-1]    |b[n-1]|
+---+...+---+----+---+...+---+----+...+---+...+---+------+
Total size: (n + 1) * size_of::<T>()
like image 133
Frxstrem Avatar answered Nov 09 '22 21:11

Frxstrem