当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Rust MaybeUninit用法及代码示例


本文简要介绍rust语言中 Union core::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 必须始终是 truefalse 。因此,创建未初始化的 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>即使TU具有相同的大小和对齐方式。此外,因为任何位值对于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-lang.org大神的英文原创作品 Union core::mem::MaybeUninit。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。