本文简要介绍rust语言中 Union std::mem::MaybeUninit
的用法。
用法
#[repr(transparent)]
pub union MaybeUninit<T> {
// some fields omitted
}
用于构造 T
的未初始化实例的包装器类型。
初始化不变量
通常,编译器假定变量已根据变量类型的要求正确初始化。例如,引用类型的变量必须对齐且非空。这是一个不变量,必须总是即使在不安全的代码中也能得到维护。因此,对引用类型的变量进行零初始化会导致瞬时未定义的行为,无论该引用是否用于访问内存:
use std::mem::{self, MaybeUninit};
let x: &i32 = unsafe { mem::zeroed() }; // undefined behavior! ⚠️
// The equivalent code with `MaybeUninit<&i32>`:
let x: &i32 = unsafe { MaybeUninit::zeroed().assume_init() }; // undefined behavior! ⚠️
编译器利用这一点进行各种优化,例如省略运行时检查和优化enum
布局。
同样,完全未初始化的内存可能有任何内容,而 bool
必须始终是 true
或 false
。因此,创建未初始化的 bool
是未定义的行为:
use std::mem::{self, MaybeUninit};
let b: bool = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
// The equivalent code with `MaybeUninit<bool>`:
let b: bool = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior! ⚠️
此外,未初始化内存的特殊之处在于它没有固定值(“fixed” 意思是“不被写入就不会改变”)。多次读取相同的未初始化字节会产生不同的结果。这使得在变量中包含未初始化的数据成为未定义的行为,即使该变量具有整数类型,否则它可以保存任何固定的位模式:
use std::mem::{self, MaybeUninit};
let x: i32 = unsafe { mem::uninitialized() }; // undefined behavior! ⚠️
// The equivalent code with `MaybeUninit<i32>`:
let x: i32 = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior! ⚠️
(请注意,关于未初始化整数的规则尚未最终确定,但在确定之前,建议避免使用它们。)
最重要的是,请记住,大多数类型除了被视为在类型级别初始化之外,还具有其他不变量。例如,一个1
-初始化std::vec::Vec被视为已初始化(在当前实现下;这不构成稳定的保证),因为编译器知道的唯一要求是数据指针必须为非空。创建这样一个Vec<T>
不会导致即时未定义的行为,但会导致大多数安全操作(包括删除它)的未定义行为。
例子
MaybeUninit<T>
用于使不安全的代码能够处理未初始化的数据。这是给编译器的一个信号,表明这里的数据可能不是被初始化:
use std::mem::MaybeUninit;
// Create an explicitly uninitialized reference. The compiler knows that data inside
// a `MaybeUninit<T>` may be invalid, and hence this is not UB:
let mut x = MaybeUninit::<&i32>::uninit();
// Set it to a valid value.
x.write(&0);
// Extract the initialized data -- this is only allowed *after* properly
// initializing `x`!
let x = unsafe { x.assume_init() };
然后编译器知道不对这段代码做出任何不正确的假设或优化。
您可以将MaybeUninit<T>
视为有点像Option<T>
,但没有任何运行时跟踪,也没有任何安全检查。
out-pointers
您可以使用 MaybeUninit<T>
实现 “out-pointers”:不是从函数返回数据,而是向其传递一个指向某个(未初始化)内存的指针以将结果放入其中。当调用者控制如何分配存储结果的内存并且您希望避免不必要的移动时,这可能很有用。
use std::mem::MaybeUninit;
unsafe fn make_vec(out: *mut Vec<i32>) {
// `write` does not drop the old contents, which is important.
out.write(vec![1, 2, 3]);
}
let mut v = MaybeUninit::uninit();
unsafe { make_vec(v.as_mut_ptr()); }
// Now we know `v` is initialized! This also makes sure the vector gets
// properly dropped.
let v = unsafe { v.assume_init() };
assert_eq!(&v, &[1, 2, 3]);
逐个元素初始化数组
MaybeUninit<T>
可用于逐个元素地初始化大型数组:
use std::mem::{self, MaybeUninit};
let data = {
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.
let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
MaybeUninit::uninit().assume_init()
};
// Dropping a `MaybeUninit` does nothing. Thus using raw pointer
// assignment instead of `ptr::write` does not cause the old
// uninitialized value to be dropped. Also if there is a panic during
// this loop, we have a memory leak, but there is no memory safety
// issue.
for elem in &mut data[..] {
elem.write(vec![42]);
}
// Everything is initialized. Transmute the array to the
// initialized type.
unsafe { mem::transmute::<_, [Vec<u32>; 1000]>(data) }
};
assert_eq!(&data[0], &[42]);
您还可以使用部分初始化的数组,这些数组可以在低级数据结构中找到。
use std::mem::MaybeUninit;
use std::ptr;
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.
let mut data: [MaybeUninit<String>; 1000] = unsafe { MaybeUninit::uninit().assume_init() };
// Count the number of elements we have assigned.
let mut data_len: usize = 0;
for elem in &mut data[0..500] {
elem.write(String::from("hello"));
data_len += 1;
}
// For each item in the array, drop if we allocated it.
for elem in &mut data[0..data_len] {
unsafe { ptr::drop_in_place(elem.as_mut_ptr()); }
}
初始化结构field-by-field
您可以使用 MaybeUninit<T>
和 std::ptr::addr_of_mut
宏逐字段初始化结构:
use std::mem::MaybeUninit;
use std::ptr::addr_of_mut;
#[derive(Debug, PartialEq)]
pub struct Foo {
name: String,
list: Vec<u8>,
}
let foo = {
let mut uninit: MaybeUninit<Foo> = MaybeUninit::uninit();
let ptr = uninit.as_mut_ptr();
// Initializing the `name` field
// Using `write` instead of assignment via `=` to not call `drop` on the
// old, uninitialized value.
unsafe { addr_of_mut!((*ptr).name).write("Bob".to_string()); }
// Initializing the `list` field
// If there is a panic here, then the `String` in the `name` field leaks.
unsafe { addr_of_mut!((*ptr).list).write(vec![0, 1, 2]); }
// All the fields are initialized, so we call `assume_init` to get an initialized Foo.
unsafe { uninit.assume_init() }
};
assert_eq!(
foo,
Foo {
name: "Bob".to_string(),
list: vec![0, 1, 2]
}
);
布局
MaybeUninit<T>
保证与 T
具有相同的大小、对齐方式和 ABI:
use std::mem::{MaybeUninit, size_of, align_of};
assert_eq!(size_of::<MaybeUninit<u64>>(), size_of::<u64>());
assert_eq!(align_of::<MaybeUninit<u64>>(), align_of::<u64>());
但请记住一个类型包含 a MaybeUninit<T>
不一定是相同的布局; Rust 通常不保证 a 的字段Foo<T>
与 a 具有相同的顺序Foo<U>
即使T
和U
具有相同的大小和对齐方式。此外,因为任何位值对于MaybeUninit<T>
编译器无法应用非零/niche-filling 优化,可能会导致更大的大小:
assert_eq!(size_of::<Option<bool>>(), 1);
assert_eq!(size_of::<Option<MaybeUninit<bool>>>(), 2);
如果 T
是 FFI-safe,那么 MaybeUninit<T>
也是如此。
尽管MaybeUninit
是#[repr(transparent)]
(表示它保证与T
),这确实不是更改之前的任何警告。Option<T>
和Option<MaybeUninit<T>>
可能仍然有不同的大小,并且类型包含类型的字段T
可能与该字段的布局(和大小)不同MaybeUninit<T>
.MaybeUninit
是联合类型,并且#[repr(transparent)]
工会是不稳定的(见跟踪问题)。随着时间的推移,确切的保证#[repr(transparent)]
工会可能会发展,并且MaybeUninit
可能会或可能不会留下#[repr(transparent)]
.也就是说,MaybeUninit<T>
将要总是保证它的大小、对齐方式和 ABI 与T
;就是这样MaybeUninit
实现保证可能会发展。
相关用法
- Rust MaybeUninit.assume_init_mut用法及代码示例
- Rust MaybeUninit.as_ptr用法及代码示例
- Rust MaybeUninit.write_slice用法及代码示例
- Rust MaybeUninit.assume_init_ref用法及代码示例
- Rust MaybeUninit.zeroed用法及代码示例
- Rust MaybeUninit.write_slice_cloned用法及代码示例
- Rust MaybeUninit.assume_init用法及代码示例
- Rust MaybeUninit.write用法及代码示例
- Rust MaybeUninit.as_mut_ptr用法及代码示例
- Rust MaybeUninit.array_assume_init用法及代码示例
- Rust MaybeUninit.uninit用法及代码示例
- Rust MaybeUninit.new用法及代码示例
- Rust MaybeUninit.uninit_array用法及代码示例
- Rust MaybeUninit.assume_init_read用法及代码示例
- Rust ManuallyDrop用法及代码示例
- Rust ManuallyDrop.into_inner用法及代码示例
- Rust ManuallyDrop.new用法及代码示例
- Rust Map用法及代码示例
- Rust Mutex.new用法及代码示例
- Rust MetadataExt.st_ctime_nsec用法及代码示例
- Rust MetadataExt.mtime_nsec用法及代码示例
- Rust MetadataExt.nlink用法及代码示例
- Rust MulAssign.mul_assign用法及代码示例
- Rust Mutex.get_mut用法及代码示例
- Rust MetadataExt.st_atime用法及代码示例
注:本文由纯净天空筛选整理自rust-lang.org大神的英文原创作品 Union std::mem::MaybeUninit。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。