如何替换不安全代码?
解读
国内 Rust 岗位面试时,面试官抛出“如何替换不安全代码”并不是想听你背诵“unsafe 关键字定义”,而是考察三层能力:
- 能否在系统设计阶段就避免 unsafe——把需求翻译成 Rust 安全抽象;
- 如果必须下沉到 unsafe,能否给出最小化、可审计的边界——让后续维护者一眼看懂“危险区”在哪;
- 能否用社区成熟方案或标准库新特性把已有的 unsafe 抹掉——体现对生态的敏感度和持续重构能力。
回答时建议按“场景→风险→替换策略→验证”四步闭环,既展示工程深度,又符合国内“能落地、能维护”的考核标准。
知识点
- 所有权与借用规则:编译期保证内存安全,80% 的 unsafe 可通过重新设计数据结构消除。
- 标准库安全抽象:Vec::with_capacity、split_at_mut、Cell/RefCell、RwLock、Arc、OnceLock 等可替代裸指针与手动内存管理。
- 零成本抽象手段:迭代器、切片模式、match 守卫、const 泛型、inline(always) 等,在性能不损失的前提下去掉 unsafe。
- FFI 边界最小化:用 extern "C" 封装层 + 透明类型 + 单点 unsafe 块,把风险收敛到模块级,对外提供 safe API。
- 编译期断言:std::assert!、static_assertions crate、const-eval,确保 unsafe 前提条件在编译期即被检查。
- 社区审计工具:cargo-geiger 统计 unsafe 行数、miri 检测未定义行为、#![forbid(unsafe_code)] 在 CI 阶段强制红线。
- 版本演进策略:Rust 每个小版本都会把原来需要 unsafe 的操作标准化(如 std::slice::from_raw_parts 的 len=0 安全化),持续升级工具链即可“白嫖”安全替代。
答案
示范思路:先给面试官一个真实可落地的三步替换框架,再举两个高频代码案例,最后说明如何量化验证。
———
步骤一:定位 unsafe 的“动机”
用 cargo-geiger 或 grep -n “unsafe” 列出所有 unsafe 块,按动机分类:
① 为了性能绕过借用检查(如裸指针解引用);
② 为了调用 C 库(FFI);
③ 为了实现自引用结构(如手写链表)。
步骤二:逐类给出国内工程环境可行的替换方案
-
性能类 unsafe
场景:热循环里用 *mut u8 批量写内存。
替换:- 用 slice::split_at_mut 把可变切片拆成多段,避免别名;
- 用 chunks_exact_mut 按块处理,编译器自动向量化;
- 若需要 SIMD,引入 packed_simd_2 或 std::simd(nightly),全程 safe API。
结果:unsafe 块从 50 行降到 0 行,benchmark 性能持平甚至提升 3%,因为 LLVM 拿到更多 noalias 信息。
-
FFI 类 unsafe
场景:调用国产 SM4 加密动态库。
替换:- 在 crate 根新建 sm4-sys 子模块,对外只暴露 extern “C” 函数签名;
- 在 sm4 高级 crate 里用 Vec::with_capacity + as_mut_ptr() 一次性把输入输出内存布局好,unsafe 仅出现在 “单点” 转换处;
- 用 #[repr(C)] 透明包装体 保证布局一致,上层业务代码 100% safe。
结果:unsafe 被限制在 3 行以内,通过 miri + Valgrind 双重 CI 门禁,符合国内金融级交付要求。
-
自引用结构 unsafe
场景:手写异步任务链,节点里存 *mut Task。
替换:- 用 pin-project-lite 把结构体标记为 !Unpin,配合 Pin<&mut Self> 消除悬垂指针;
- 若仍需内部可变性,用 tokio::sync::UnsafeCell 的 safe 封装,或改用 futures-intrusive::LinkedList 已审计实现;
- 最终 unsafe 行数归零,且无需 GC。
步骤三:量化验证与持续防护
- CI 阶段加 #![deny(unsafe_code)] 到业务 crate,只允许 sm4-sys 这种底层 crate 打开 unsafe;
- 用 cargo-udeps 检查是否误引入带 unsafe 的过期依赖;
- 发版前跑 cargo-audit + miri + sanitizers,输出 PDF 报告给国内甲方,一次通过等保测评。
———
一句话总结:“先拆动机,再套标准库或社区轮子,最后用 CI 门禁把 unsafe 关进笼子。” 面试官听到“量化验证”和“等保测评”关键词,基本就认可你的工程化能力了。
拓展思考
-
反向场景:何时必须保留 unsafe?
国内做操作系统内核或裸机嵌入式时,启动阶段要写 bootloader 页表,必须操作物理地址映射。此时策略是:- 把 unsafe 集中到 arch::x86_64::boot 子模块,对外只暴露 init_mmu() -> Result<(), BootError> 这类 safe API;
- 用 const_assert! 在编译期检查页对齐,减少运行时 UB;
- 通过 “安全白皮书” 文档化不变量,满足国产操作系统招投标的合规审计。
-
团队规模化治理
在几十人协同的国内大厂,建立 “unsafe 评审委员会”:任何新增 unsafe 需开 30 分钟评审会,提交 miri 报告和性能对比,合并请求必须两人签字。半年后可把 unsafe 密度从 0.8% 压到 0.1%,同时性能零回退。 -
Rust 版本红利
关注 Rust RFC 3317(strict_provenance) 和 ptr_metadata 特性,未来可彻底告别 *mut 与 usize 乱转,一旦 stable,直接升级工具链即可再删掉一批 unsafe,把“技术债”变成“技术红利”。