15个版本

0.6.2 2023年11月20日
0.5.1 2022年8月22日
0.5.0 2022年1月4日
0.4.1-dev2021年1月26日
0.1.0 2019年9月1日

#14 in 内存管理

Download history 23811/week @ 2024-04-08 38264/week @ 2024-04-15 39218/week @ 2024-04-22 40601/week @ 2024-04-29 32740/week @ 2024-05-06 41432/week @ 2024-05-13 45037/week @ 2024-05-20 46122/week @ 2024-05-27 47154/week @ 2024-06-03 34408/week @ 2024-06-10 35580/week @ 2024-06-17 49600/week @ 2024-06-24 35751/week @ 2024-07-01 31168/week @ 2024-07-08 38722/week @ 2024-07-15 39680/week @ 2024-07-22

145,732 每月下载量
33 个Crates中(直接使用4个) 使用

Zlib OR MIT OR Apache-2.0

120KB
1.5K SLoC

::uninit

一组用于更安全地使用未初始化内存的工具。

Latest version Documentation License MSRV


许多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”。

      目前只有两种例外/有效用例

      • 或者 type T = [MaybeUninit<U>; N]

      • 或者 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

这全在于 延迟初始化模式

  1. 创建

    创建一个 MaybeUninit<T>,例如,使用 MaybeUninit::<T>::uninit()

    use ::core::mem::MaybeUninit;
    
    let mut x = MaybeUninit::<i32>::uninit();
    
  2. (延迟) 初始化

    在极大程度上小心避免意外地创建(即使只是一瞬间)一个 &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);
      
  3. 类型级别升级

    一旦我们确实知道内存已经被初始化,我们就可以将 MaybeUninit<T> 类型升级为完全的 T 类型

问题

如您所见,通过限制性和不灵活的类型(&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 的目标是双重的

#![no_std] 友好

只需在你的 Cargo.toml 文件中禁用默认启用的 "std" 功能

[dependencies]
uninit = { version = "x.y.z", default-features = false }

依赖项

~0–275KB