本节摘要:unsafe 块并不关闭借用检查器,只是授予五种超能力:解引用裸指针、调用 unsafe 函数、访问可变静态量、实现 unsafe Trait、访问联合体。本节讲五种超能力、越权的责任条款与"安全抽象"的封装模式。读完你能安全地读懂和包裹 Unsafe 代码。
let mut num = 5; let r1 = &num as *const i32; // 裸指针:可同时存在多个 let r2 = &mut num as *mut i32; unsafe { println!("r1 = {}", *r1); // 解引用裸指针:越权区 }
裸指针与引用的区别正是第 1 章的判词:引用是"带契约的地址",裸指针是"光秃秃的地址"——不保证有效、不查借用规则、可能为空。除此之外还有四项:调用其他 unsafe 函数或 FFI、读写可变静态量、实现 unsafe Trait(如 Send 与 Sync 的手动实现)、访问 union 字段。
unsafe 块是一份申明书:写它的人向所有调用者承诺"我已人工验证这里满足编译器无法验证的不变量"。所以规矩是:
标准库的 Vec 全靠裸指针与手动内存管理实现,但用户侧完全安全——因为 Vec 的方法把不变量(长度不越界、缓冲区有效)维护在内部。这叫安全抽象:Unsafe 住在底层,安全住在 API 上。生态里的锁、原子操作、解析器内核全是这个模式。

unsafe 的全部能力收敛为五项,每项都对应一条"编译器不再担保、由你书面担保"的不变量。
fn main() { let mut sealed = vec![10u32, 20, 30]; // 权限一:解引用裸指针(*const / *mut) let p: *const u32 = sealed.as_ptr(); let first = unsafe { *p }; // 担保:指针有效且未失效 println!("首格 {}", first); unsafe { // 权限二:调用 unsafe 函数(含 FFI) roar(); // 权限三:访问或修改可变静态量 COUNTER += 1; // 权限四:实现 unsafe trait(第 8 章 Send/Sync 的表态入口) } // 权限五:访问 union 字段 —— 内存布局重判,本节不展 println!("计数 {}", COUNTER); } unsafe fn roar() { println!("记录已直接落盘"); } static mut COUNTER: u32 = 0;
五项之外 unsafe 不增加任何能力——不能绕过借用检查的报错(那些检查仍然在)、不能关掉 drop。它是"我知道我在做什么"的书面声明,范围以块为界,越小说明契约越清晰。
工程铁律是 unsafe 块封装在 safe 函数内部,函数签名就是安全契约,unsafe 细节不外泄。
use std::slice; fn split_at_total(buf: &[u8], mid: usize) -> (&[u8], &[u8]) { assert!(mid <= buf.len()); // 安全前置:挡住越界 let head = unsafe { slice::from_raw_parts(buf.as_ptr(), mid) }; let (a, b) = buf.split_at(mid); // 对照组:标准库已给安全版 let _ = head; (a, b) }
上面刻意并排了裸指针版与安全版——绝大多数"想用 unsafe"的场合标准库已有安全等价物,先用检索代替手写。真正需要 unsafe 的席位集中在:FFI 边界、自管内存的数据结构(Vec 内部)、以及性能证明成立的热点。
裸指针是 Send/Sync 双否决的类型,携带两条不可破的规则。其一:派生自引用的裸指针,只在引用仍存活期间可用——原值 drop 后指针作废,用之即未定义行为,且往往无报错。其二:可变裸指针的读写互斥要人工保证,编译器不再排班。审查 unsafe 代码就审这两条:指针的来历、指针的有效期,说得清就立得住,说不清就是隐患。
每个 unsafe 块的注释应包含三要素:担保的不变量(为什么这步安全)、失效条件(什么改动会破坏它)、验证手段(测试或断言如何覆盖)。评审 unsafe 代码按固定清单走:指针来历是否明确;指针有效期是否与属主绑定;跨边界数据是否满足布局约定;是否有 safe 包装层隔离。Mirai 式形式化工具之外,实践中最有效的是把 unsafe 块的行数当作预算管理——每新增一行都要在评审里说出理由,块数只减不增的方向感,比任何工具都更能约束风险。
"性能优化应当先写 unsafe 版本再证明收益"——判错。正序永远是:基准测试锁定热点、优先安全等价改法(算法、数据布局、避免克隆),unsafe 是穷尽安全手段后的最后一步,且必须附带优化前后数据。把这条纪律写进团队规范,unsafe 在代码库里的占比自然收敛到必要最小。
成熟开源代码库里 unsafe 行数占比通常在个位数百分比:标准库偏高(底层设施职责所在),应用项目趋近于零。这个数字当参考线而非硬指标,但方向感明确——业务代码里 unsafe 占比超过百分之一就该开评审会。配套的观测手段:构建时统计 unsafe 块数量的脚本、评审清单里的担保注释检查。把配额与观测做成机制,unsafe 的"必要最小"才不依赖个人自觉。
本章五项权限各有出处:裸指针解引用承接第 3 章的产权(担保指针来历与期限);unsafe 函数调用承接第 6 章 FFI 判例;可变静态量承接第 8 章的共享状态纪律;unsafe trait 实现承接 Send/Sync 表态;union 字段承接 repr(C) 的布局判例。终审庭没有新法条,审的全是前九章的旧纪律在无担保区的自觉执行——这也是它放在全书最后的原因。