bigywater.tech
Rust · 118

MaybeUninit

Rust 不让你读没初始化的内存,可有时又需要避开清零的成本或逐个构造数组元素。MaybeUninit 就是这个出口。这篇验证它的大小、用法、drop 规则,并量出清零的代价。

8 字节
MaybeUninit 的大小(没有初始化标志)
不 drop
MaybeUninit 离开作用域时对内部值的处理(泄漏)
11.07 对 0.93 µs
1 MiB 缓冲区:清零与不清零的创建耗时

为什么需要 MaybeUninit

实测 · 本机执行输出
  源码:
      1 | fn main() { let x: u32; println!("{}", x); }
  编译:失败
    error[E0381]: used binding `x` isn't initialized
     --> <实验目录>/src/u118a.rs:1:40
      |
    1 | fn main() { let x: u32; println!("{}", x); }
      |                 -                      ^ `x` used here but it isn't initialized
      |                 |
      |                 binding declared here but left uninitialized
      |
    help: consider assigning a value
      |
    1 | fn main() { let x: u32 = 42; println!("{}", x); }
      |                        ++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0381`.
  编译:成功
  运行(退出码 0):
      size_of::<u64>() = 8  size_of::<MaybeUninit<u64>>() = 8  align 同样 8 / 8
      size_of::<Option<u64>>() = 16(Option 要标签)而 MaybeUninit<u64> 没有额外字节:它不记录是否已初始化
      write 之后 assume_init = 42

unsafe 的边界见unsafe 的安全边界。

逐个初始化数组

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      逐个 write 之后整体转成 [String; 4]:["item-0", "item-1", "item-4", "item-9"]
      对照:std::array::from_fn 的安全写法得到相同结果: true

drop 规则

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      1) MaybeUninit<Noisy> 离开作用域:
         (没有看到 Noisy(1) drop —— MaybeUninit 不负责析构,值泄漏了)
      2) 手动 assume_init_drop:
      Noisy(2) drop
      3) 数组只初始化了前 2 个,只 drop 这 2 个:
      Noisy(10) drop
      Noisy(11) drop

Drop 的运行规则见Drop Trait 与资源释放。

省掉清零

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      vec![0u8; 1 MiB](清零)          每次    11.07 µs
      with_capacity + set_len(不清零) 每次     0.93 µs(只写了 1/4096 的字节,其余保持未初始化)

vec![0u8; N] 可能走系统的清零分配(calloc),这里没有区分;数字是本机 release 微基准,且只写了极少数字节,仅用于说明量级。读取从未写入的内存是未定义行为,本文没有演示。

分步演示

MaybeUninit
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:读取未初始化内存的实际后果(UB,不演示)、assume_init_read / assume_init_ref 的用法、Miri 对 MaybeUninit 误用的检测(需要 nightly)、mem::zeroed 对非零合法类型的 UB、with_capacity 的 spare_capacity_mut 接口
    京ICP备2026067502号-1京公网安备11010502063487号