2010 年代的编程语言中,Rust 可能是最受赞誉的。其主要卖点是能将速度、低级控制与高水平的抗 bug 性(即安全性)相结合,主要创新是借用检查器,可在不增加运行时成本的情况下实现垃圾回收语言的内存安全性。
借用检查器的基本问题
- 抽象层面,借用检查器需在编译时知道所有引用的生命周期,这不现实,因为生命周期常是运行时属性。
- 算法层面,它执行的所有权模型过于严格,拒绝了太多行为良好的程序。
- 实现层面,当前的借用检查器不完整,常拒绝符合所有权模型的程序。
- 借用检查器的问题往往不易展示,只有在现有项目需要修改所有权结构而被拒绝时才会感受到。
借用检查器失败的例子
- 如对结构体不同字段的同时可变引用会被拒绝,尽管它们不指向同一数据,这是借用检查器规则实现不准确导致的。
- 跨函数时,借用检查器可能错误地拒绝代码,即使函数内部逻辑不会相互干扰。
- 在函数的不同分支中,借用检查器也可能无法正确推理。
“足够智能的借用检查器”
- 有人认为上述例子是实现限制,未来会改善,如采用非词法生命周期和新的 Polonius 公式能提高准确性,但 Polonius 已研发 7 年仍未完成,且借用检查器永远不会“完整”,因为它要对代码进行推理,而程序难以做到足够深入。
规则本身不便于使用
- 有时抽象所有权模型的实现存在限制,有时模型本身就不适合程序,如对临时值的引用限制、混合所有权结构的限制等,这些在垃圾回收语言中不是问题,但在 Rust 中是自找的。
- 有人认为借用检查器的痛苦是“前期痛苦”,能保证内存安全,但实际经验并非如此,很多时候是不必要的限制。
- 对于经验丰富的 Rustaceans 来说,一些解决借用检查器问题的方法看似简单,但在大规模代码中可能是挑战,而且会导致“逃避现实”,让人更关注满足借用检查器而不是解决实际问题。
为什么不……
- 解决借用检查器问题的常见方法有使用更少的引用并复制数据(或“克隆”)、使用
Rc / Arc / RefCell / Box
、使用索引代替引用等,但这些方法都有各自的问题。
Rust 的安全性部分归功于借用检查器
- Rust 的安全性应归功于其广泛的良好设计和风格,如广泛使用枚举和穷尽模式匹配、使用自定义类型在类型系统中编码信息、强制使用关键字参数构造结构体等,其标准库也有不易误用的 API。
- 具有类似特征但使用垃圾回收器的语言也能有大部分 Rust 的正确性,如 OCaml 和 Haskell。
好吧,借用检查器也不是“一无是处”
- 没有借用检查器,要么手动管理内存(繁琐且易出错),要么使用垃圾回收器,后者有延迟和内存效率低的问题,对于非底层应用,这些问题不适用。
- 比较垃圾回收器和借用检查器的性能并不简单,GC 在某些情况下会导致性能下降,但在某些基准测试中,Julia 的 GC 比 Rust 的确定性销毁更快。
- 借用检查器能独特地防止一些垃圾回收器无法防止的 bug,如多线程代码中的数据竞争,但在单线程代码中,防止外部持有引用的突变并不常见。此外,借用检查器还有一些意外的小好处,如解锁优化等。
结论
在日常工作中,在 Julia、Python 和 Rust 之间切换时,会怀念各自的优点,但绝对不会怀念借用检查器。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。