#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