15个版本
0.6.2 | 2023年11月20日 |
---|---|
0.5.1 | 2022年8月22日 |
0.5.0 | 2022年1月4日 |
0.4.1-dev | 2021年1月26日 |
0.1.0 |
|
#14 in 内存管理
145,732 每月下载量
在 33 个Crates中(直接使用4个) 使用
120KB
1.5K SLoC
::uninit
一组用于更安全地使用未初始化内存的工具。
许多Crates试图复制C "优化"模式,例如在不考虑Rust类型比C类型更微妙的情况下处理未初始化内存。
例如,以下代码是 未定义行为!
use ::core::mem;
let mut array: [u8; 256] = unsafe { mem::uninitialized() };
for (i, x) in array.iter_mut().enumerate() {
*x = i as u8;
}
实际上,它创建带有未初始化内存的 u8
,这在Rust模型中目前没有定义的行为(见 "What The Hardware Does" is not What Your Program Does: Uninitialized Memory,by Ralf Jung),然后创建对这些无效 u8
的Rust引用,这些引用也变成了无效的。
不要使用 mem::uninitialized
!
事后看来,在Rust(即使标记为 unsafe
)的core
库中提供mem::uninitialized
函数是语言最严重的错误之一。确实,该函数是通用的,并且仅由Sized
约束,并且事实证明,除了零大小类型或后来引入的MaybeUninit
<T>
之外,所有其他对此函数的调用都是不安全的(即时UB)。
请注意,触发这种未定义行为(UB)的方式不止一种,并不一定需要显式使用 mem::uninitialized
::<T>()
,例如
-
use ::core::mem::MaybeUninit; type T = u8; // for the example unsafe { MaybeUninit::<T>::uninit() // Uninitialized `MaybeUninit<T>` => Fine .assume_init() // Back to an uninitialized `T` => UB };
-
这完全等同于调用
mem::uninitialized::<T>()
,这破坏了T
的 有效性 不变式,从而导致“即时 UB”。目前只有两种例外/有效用例
-
或者
T
是一个填充的 ZST(然而,这可能会破坏与类型属性相关的不变性,一旦见证到这种破坏的不变性,就会导致 UB)。
-
是的,使用
MaybeUninit
比仅仅更改函数调用更微妙。
-
-
let mut vec: Vec<u8> = Vec::with_capacity(100); // Fine unsafe { vec.set_len(100); // we have an uninitialized [u8; 100] in the heap // This has broken the _safety_ invariant of `Vec`, but is not yet UB // since no code has witnessed the broken state } let heap_bytes: &[u8] = &*vec; // Witness the broken safety invariant: UB!
相反,(你可以)使用 MaybeUninit
因此,操作未初始化内存的解决方案是使用 MaybeUninit
:特殊类型 MaybeUninit<T>
并不假设其支持内存已被初始化/ 无论 T
如何,未初始化的 MaybeUninit<T>
的行为都是定义良好的。
如何正确使用 MaybeUninit
这全在于 延迟初始化模式
-
创建
创建一个
MaybeUninit<T>
,例如,使用MaybeUninit::<T>::uninit()
use ::core::mem::MaybeUninit; let mut x = MaybeUninit::<i32>::uninit();
-
(延迟) 初始化
在极大程度上小心避免意外地创建(即使只是一瞬间)一个
&T
、&mut T
,甚至是一个未初始化的T
(这将导致 UB),我们可以通过写入(从而初始化)未初始化的内存,通过&mut MaybeUninit<T>
-
直接,例如
use ::core::mem::MaybeUninit; let mut x = MaybeUninit::<i32>::uninit(); x = MaybeUninit::new(42); assert_eq!(42, unsafe { x.assume_init() });
-
或者通过原始的
*mut T
指针(与 Rust 引用不同,原始指针并不假设它们指向的内存是有效的)。例如use ::core::mem::MaybeUninit; let mut x = MaybeUninit::<i32>::uninit(); unsafe { x.as_mut_ptr().write(42); assert_eq!(x.assume_init(), 42); }
-
或者,如果您使用此存储库的工具,通过将
&mut MaybeUninit<T>
升级为名为&out T
的类型,称为Out
<T>
#![forbid(unsafe_code)] // no unsafe! use ::core::mem::MaybeUninit; use ::uninit::prelude::*; let mut x = MaybeUninit::uninit(); let at_init_x: &i32 = x.as_out().write(42); assert_eq!(at_init_x, &42);
-
-
类型级别升级
一旦我们确实知道内存已经被初始化,我们就可以将
MaybeUninit<T>
类型升级为完全的T
类型-
按值(
MaybeUninit<T> -> T
):.assume_init()
-
按共享引用(
&MaybeUninit<T> -> &T
):.assume_init_by_ref()
-
按唯一引用(
&mut MaybeUninit<T> -> &mut T
):.assume_init_by_mut()
-
问题
如您所见,通过限制性和不灵活的类型(&mut MaybeUninit<T>
/ *mut T
)来操作 MaybeUninit
以初始化其内容。
因此,大多数 API 都不提供输出/写入未初始化内存的方法。
这最终导致许多人跳过步骤 .2
之前的步骤 .3
:与 &mut T
一起工作比与 *mut T
一起工作要舒适得多,尤其是在涉及数组、切片和向量时。因此,人们最终会执行未定义行为(UB)。
这种情况下的一个最严重的违规者是 Read
特性
use ::std::io;
pub trait Read {
fn read (&mut self, buf: &mut [u8]) -> Result<usize, io::Error>;
// ...
}
也就是说,无法将 .read()
读取到未初始化的缓冲区中(这需要一个接受 (*mut u8, usize)
对的 API,或者,顺便提一下,更符合人体工程学的等效 API,比如 &out [u8]
)。
输入 ::uninit
因此,这个 crate 的目标是双重的
-
它提供了创建未初始化缓冲区的便捷方法。
例如
-
它试图提供与普遍使用的 API 相当的 API,例如
Read
的,但能够与这种未初始化的缓冲区一起工作。例如
#![no_std]
友好
只需在你的 Cargo.toml
文件中禁用默认启用的 "std"
功能
[dependencies]
uninit = { version = "x.y.z", default-features = false }
依赖项
~0–275KB