本页目录
unsafe 块与 Soundness 契约
unsafe关键字不"禁用"安全检查,而是把本由编译器保证的安全契约转交给程序员保证——你可以解引用裸指针、调用 unsafe 函数、访问可变静态变量,但你必须确保无论调用方怎么做,都不会触发 UB(未定义行为)。写 unsafe 代码的真正难点不是"能写出来",而是为它封装一个安全的抽象外壳。
unsafe 不是"禁用所有安全检查"
unsafe {} 块只开启 5 项能力,其余的 Rust 安全检查依然生效:
- 解引用裸指针 (
*const T,*mut T) - 调用
unsafe fn - 访问或修改
static mut变量 - 实现
unsafe trait(如Send,Sync,GlobalAlloc) - 访问
union的字段
Borrow checker 在 unsafe 块内仍然有效——你不能创建两个 &mut T 指向同一数据,这是 UB,编译器在 unsafe 中依然会拒绝。
理解 unsafe 的关键:它不是"我来承担所有安全责任",而是"我承担编译器无法自动验证的那部分责任"。
Soundness: 安全的边界
unsafe fn 的签名定义了契约。调用者必须满足前置条件,否则可能产生 UB。标准库中到处是这样的模式:
// 对外安全, 内部 unsafe — 调用者只需要传对参数, 不需要写 unsafe
如果 assert 通过,下面的 unsafe 操作就是 sound 的——mid <= len 保证了两段 slice 不重叠。如果 assert 失败,直接 panic——不会进入 unsafe 区域。这是标准库最常用的模式:safe code 验证条件,unsafe code 在条件成立的前提下执行。
关键:unsafe 代码的 soundness 不仅依赖于 unsafe 块内的逻辑,还依赖于外部 safe code 维护的不变量。如果你写的 unsafe 代码假设了某个数据结构的不变量(如 len <= cap),那么外部所有修改该数据结构的 safe 代码都必须维护这个不变量。
UB (Undefined Behavior): 必须避免的清单
- 解引用空指针、悬垂指针、未对齐指针
- 违反借用规则(即使在 unsafe 中)
- 数据竞争
- 用错误的 ABI 调用函数
- 产生不合法的值:
bool非 0 非 1、引用为空、char是 surrogate - unwind 穿过 FFI 边界(
extern "C"函数中 panic)
Miri: 自动化 UB 检测
Miri 在 MIR 级别解释执行 Rust,追踪 provenance 和 borrow 状态:
可以检测:use-after-free、out-of-bounds access、data races、alignment violations。任何有 unsafe 的 crate 都应该跑 Miri——它不能证明 soundness,但能发现明显的 UB。
参考
- Rustonomicon: "What Unsafe Can Do", Soundness requirements
- Rust Reference: Behavior considered undefined
- Miri: github.com/rust-lang/miri
Keywords: unsafe, soundness, UB, Miri, safety contract, invariant, raw pointer