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都是未定义行为。