以下代码是否正确?
#![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();
}
}
我应该注意miri可以毫无问题地运行它。
答案 0 :(得分:5)
更正:在一般情况下,下面的答案仍然成立,但是在MaybeUninit
的情况下,有一些方便的关于内存布局的特殊情况使得这样做实际上很安全:
首先,MaybeUninit
的文档中有一个layout部分,说明了这一点
MaybeUninit<T>
的大小和对齐方式与T
相同。
其次,语言参考说明了array layouts:
对数组进行布局,以使数组的
nth
元素与数组的开头偏移n * the size of the type
个字节。[T; n]
数组的大小为size_of::<T>() * n
,对齐方式为T
。
这意味着MaybeUninit<[T; n]>
的布局和[MaybeUninit<T>; n]
的布局是相同的。
原始答案:
据我所知,这是可能会起作用但不能保证的事情之一,并且 可能会受到特定于编译器或平台的行为的影响。
MaybeUninit
在current source中的定义如下:
#[allow(missing_debug_implementations)]
#[unstable(feature = "maybe_uninit", issue = "53491")]
pub union MaybeUninit<T> {
uninit: (),
value: ManuallyDrop<T>,
}
由于它没有用#[repr]
属性标记(与例如ManuallyDrop
相对),因此它是默认表示形式,其引用为says this:
没有repr属性的标称类型具有默认表示形式。非正式地,这种表示也称为锈表示。
此表示不能保证数据布局。
为了从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>]
有一个{{1} },用于数组中的每个b
:
MaybeUninit<T>