Разумно ли преобразовывать MaybeUninit в [MaybeUninit; N]?

#rust

#Ржавчина

Вопрос:

Звучит ли следующий код?

 #![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(amp;mut t[0], mem::MaybeUninit::uninitialized()).into_initialized();
        mem::replace(amp;mut t[1], mem::MaybeUninit::uninitialized()).into_initialized();
    }
}
  

Я должен отметить, что мири запускает его без проблем.

Ответ №1:

Исправление: приведенный ниже ответ по-прежнему выполняется в общем случае, но в случае MaybeUninit есть несколько удобных особых случаев с расположением памяти, которые делают это действительно безопасным:

Во-первых, в документации для MaybeUninit есть раздел layout, в котором указано, что

MaybeUninit<T> гарантированно будет иметь тот же размер и выравнивание, что и T .

Во-вторых, в языковой справке сказано это о макетах массивов:

Массивы расположены таким образом, что nth элемент массива смещен от начала массива на n * the size of the type байты. Массив [T; n] имеет размер size_of::<T>() * n и такое же выравнивание T .

Это означает, что расположение MaybeUninit<[T; n]> и разметка [MaybeUninit<T>; n] одинаковы.


Оригинальный ответ:

Из того, что я могу сказать, это одна из тех вещей, которые, вероятно, будут работать, но не гарантированы и могут зависеть от поведения, зависящего от компилятора или платформы.

MaybeUninit определяется следующим образом в источнике тока:

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

Поскольку он не отмечен #[repr] атрибутом (в отличие от, например, ManuallyDrop ), он находится в представлении по умолчанию, о котором в ссылке сказано следующее:

Номинальные типы без атрибута repr имеют представление по умолчанию. Неофициально это представление также называется представлением rust.

Нет никаких гарантий компоновки данных, созданной этим представлением.

Для преобразования из Wrapper<[T]> в [Wrapper<T>] должно быть так, что расположение памяти Wrapper<T> точно совпадает с расположением памяти T . Это относится к ряду оболочек, таких как упомянутые ранее ManuallyDrop , и они обычно будут помечены #[repr(transparent)] атрибутом.

Но в данном случае это не обязательно верно. Поскольку () это тип нулевого размера, вполне вероятно, что компилятор будет использовать тот же формат памяти для T и MaybeUninit<T> (и именно поэтому это работает у вас), но также возможно, что компилятор решит использовать какой-то другой формат памяти (например, в целях оптимизации), и в этом случае преобразование больше не будет работать.


В качестве конкретного примера компилятор может выбрать использование следующего расположения памяти для MaybeUninit<T> :

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

Согласно приведенной выше цитате, компилятору разрешено это делать. В этом случае [MaybeUninit<T>] и MaybeUninit<[T]> имеют разные схемы расположения памяти, поскольку MaybeUninit<[T]> имеют одну b для всего массива, в то время как [MaybeUninit<T>] имеют по одной b для каждого MaybeUninit<T> в массиве:

 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>()
  

Комментарии:

1. Имеет ли это значение, если я использую MaybeUninit<[MaybeUninit<T>]> ?

2. @llogiq Нет, так не должно быть. Я добавил конкретный пример, чтобы попытаться пояснить, например, что я здесь имел в виду.

3. @llogiq По-видимому, я пропустил часть документации для MaybeUninit , когда я первоначально писал свой ответ. Мне показалось, что я изначально был неправ (по крайней мере, в конкретном случае MaybeUninit ), и я обновил свой ответ, чтобы отразить это.

4. Я думаю, MaybeUninit так было #[repr(transparent)] с Rust 1.37: github.com/rust-lang/rust/blob/master/RELEASES.md#libraries-25