本文簡要介紹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。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。