直接回答:安全抽象原则:unsafe 代码块内部可以越界(解引用裸指针、调 unsafe fn),但必须向上暴露完全安全的 API——调用方不传任何特殊条件就不会触发 UB。契约的两半:unsafe fn 的“调用前提”写在文档里由调用方保证(如指针必须有效对齐);安全 fn 则不许有任何未文档化的前提。“局部契约、全局责任”指 UB 是全局判定:一处 unsafe 破坏不变量,崩的可能是千里之外的 safe 代码,所以责任不随 unsafe 块边界结束。

展开解析:写安全抽象的检查清单:第一,列出模块维护的内部不变量(如 Vec 的 len ≤ capacity、指针非空且对齐);第二,验证每个公开安全方法在所有合法输入下不变量保持——注意“合法输入”包括恶意构造,safe 代码的信任边界是类型系统而非“用户不会乱来”;第三,panic 路径也要保持——中途 panic 留下半更新状态,后续 safe 代码读到就 UB,所以要么先校验后修改要么用 guard 回滚;第四,unsafe trait(如 Send/Sync)的实现是全局断言,实现错了整个程序失去线程安全保证。工具链:Miri 跑测试集抓 UB、#[deny(unsafe_op_in_unsafe_fn)] 显式标记每个 unsafe 操作、Kani/model checking 做验证。反模式:unsafe 块写得极大“图省事”——应缩到最小必要操作;把调用方校验责任藏进安全函数签名里(参数看着普通实际要求对齐)。标准库自身就是范本:Vec、String 内部大量 unsafe,外部绝对安全。

追问方向:内部可变性的边界由谁定义?为什么 safe 代码逻辑错误也能让 unsafe 变 UB?

(约 480 字)