ch15-05-interior-mutability.md commit 74edb8dfe07edf8fdae49c6385c72840c07dd18f
内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改
变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊
Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运
行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所
涉及的 unsafe 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
让我们通过遵循内部可变性模式的 RefCell 类型来开始探索。
通过 RefCell 在运行时检查借用规则
不同于 Rc,RefCell 代表其数据的唯一的所有权。那么是什么让 RefCell 不同于像 Box 这
样的类型呢?回忆一下第四章所学的借用规则:
- 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 之一(而不是两者)。
- 引用必须总是有效的。
对于引用和 Box,借用规则的不可变性作用于编译时。对于 RefCell,这些不可变性作用于 运行
时。对于引用,如果违反这些规则,会得到一个编译错误。而对于 RefCell,如果违反这些规则程序
会 panic 并退出。
在编译时检查借用规则的优势是这些错误将在开发过程的早期被捕获,同时对运行时没有性能影响,因
为所有的分析都提前完成了。为此,在编译时检查借用规则是大部分情况的最佳选择,这也正是其为何
是 Rust 的默认行为。
相反在运行时检查借用规则的好处则是允许出现特定内存安全的场景,而它们在编译时检查中是不允许
的。静态分析,正如 Rust 编译器,是天生保守的。但代码的一些属性不可能通过分析代码发现:其中最
著名的就是 停机问题(Halting Problem),这超出了本书的范畴,不过如果你感兴趣的话这是一个值
得研究的有趣主题。
因为一些分析是不可能的,如果 Rust 编译器不能通过所有权规则编译,它可能会拒绝一个正确的程序;
从这种角度考虑它是保守的。如果 Rust 接受不正确的程序,那么用户也就不会相信 Rust 所做的保证了。
然而,如果 Rust 拒绝正确的程序,虽然会给程序员带来不便,但不会带来灾难。RefCell 正是用于
当你确信代码遵守借用规则,而编译器不能理解和确定的时候。
类似于 Rc,RefCell 只能用于单线程场景。如果尝试在多线程上下文中使用RefCell,会得到
一个编译错误。第十六章会介绍如何在多线程程序中使用 RefCell 的功能。
如下为选择 Box,Rc 或 RefCell 的理由:
• Rc 允许相同数据有多个所有者;Box 和 RefCell 有单一所有者。
• Box 允许在编译时执行不可变或可变借用检查;Rc仅允许在编译时执行不可变借用检查;
RefCell 允许在运行时执行不可变或可变借用检查。
• 因为 RefCell 允许在运行时执行可变借用检查,所以我们可以在即便 RefCell 自身是不可变
的情况下修改其内部的值。
在不可变值内部改变值就是 内部可变性模式。让我们看看何时内部可变性是有用的,并讨论这是如何成
为可能的。