Rust - MaybeUninit未初始化内存
Rust 1.39中废弃了std::mem::uninitialized
(也即core::mem::uninitialized
),因为它可能引发未定义行为。现在应当使用std::mem::MaybeUninit
(也即core::mem::MaybeUninit
)替代之。
未定义的行为¶
最简单的例子就是未初始化的bool
:
use std::mem;
let bool_value = unsafe { mem::uninitialized::<bool>() };
// 错误:在 bool_value 真正初始化之前就使用它的值
if bool_value {
// ......
}
在目标平台上,未初始化的内存可能是不确定值,也有可能有某种初始值,例如全零、烫烫烫烫烫
、屯屯屯屯屯
。无论如何,在Rust语言的层面,在bool_value
真正初始化之前,它的值是不确定的。如果其实际值为零,就会被当成false
;如果其实际值非零,就会被当成true
(1)。此时使用bool_value
就会引发未定义的行为。
对开发人员的要求¶
可见,mem::uninitialized
需要开发人员自行保证,不要「在真正初始化之前使用未初始化内存」。
这种错误用法很有可能是隐晦的:
use std::io::{self, Read};
use std::mem;
#[repr(packed, C)]
struct MyRecord {
field1: u16,
field2: u32,
}
impl MyRecord {
pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
let mut record = mem::uninitialized::<MyRecord>();
src.read_exact(
std::slice::from_raw_parts_mut(
&mut record as *mut _ as *mut u8,
mem::size_of::<MyRecord>()
)
)?;
Ok(record)
}
}
impl Drop for MyRecord {
fn drop(&mut self) {
// ......
}
}
如果read_exact
出错,就会在record
没有有效值的情况下调用drop
。 为此,必须先将内容保存到一个没有drop
行为的结构,然后在获取了有效值之后再构造需要的结构:
impl MyRecord {
pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
let mut raw = [0u8; mem::size_of::<MyRecord>()];
src.read_exact(&mut raw)?;
Ok(mem::transmute(raw))
}
}
mem::MaybeUninit¶
Rust官方推荐用mem::MaybeUninit
来实现未初始化的内存:
impl MyRecord {
pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
let mut record = mem::MaybeUninit::<MyRecord>::uninit();
src.read_exact(
std::slice::from_raw_parts_mut(
record.as_mut_ptr() as *mut u8,
mem::size_of::<MyRecord>()
)
)?;
Ok(record.assume_init())
}
}
MaybeUninit<MyRecord>
本身的失效并不会触发MyRecord
的drop
,因此MaybeUninit::uninit
是safe的。
mem::MaybeUninit
是用mem::ManuallyDrop
实现的(这里省略了一些细节):
#[lang = "manually_drop"]
struct ManuallyDrop<T> { value: T }
struct MaybeUninit<T> {
value: ManuallyDrop<T>,
// ......
}
在对应的lang item实现之前,ManuallyDrop
是用union实现的:
这利用了「即使union的field是Drop
的,编译器也不会隐式为union实现Drop
」这一特点。
只有1才会被认为是true,其他非零值作为bool都是未定义行为。