RefCell<T>和内部可变性模式

ch15-05-interior-mutability.md
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许。内部可变性模式涉及到在数据结构中使用unsafe代码来绕过 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习他们。内部可变性模式用于当你可以确保代码在运行时也会遵守借用规则,哪怕编译器也不能保证的情况。引入的unsafe代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

让我们通过遵循内部可变性模式的RefCell<T>类型来开始探索。

RefCell<T>拥有内部可变性

不同于Rc<T>RefCell<T>代表其数据的唯一的所有权。那么是什么让RefCell<T>不同于像Box<T>这样的类型呢?回忆一下第四章所学的借用规则:

  1. 在任意给定时间,只能拥有如下中的一个:
  • 一个可变引用。
  • 任意属性的不可变引用。
  1. 引用必须总是有效的。

对于引用和Box<T>,借用规则的不可变性作用于编译时。对于RefCell<T>,这些不可变性作用于运行时。对于引用,如果违反这些规则,会得到一个编译错误。而对于RefCell<T>,违反这些规则会panic!

Rust 编译器执行的静态分析时天生保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。

因为一些分析是不可能的,Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不变,但不会带来灾难。RefCell<T>正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。

类似于Rc<T>RefCell<T>只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用RefCell<T>的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用RefCell<T>,会得到一个编译错误。

对于引用,可以使用&&mut语法来分别创建不可变和可变的引用。不过对于RefCell<T>,我们使用borrowborrow_mut方法,它是RefCell<T>拥有的安全 API 的一部分。borrow返回Ref类型的智能指针,而borrow_mut返回RefMut类型的智能指针。这两个类型实现了Deref所以可以被当作常规引用处理。RefRefMut动态的借用所有权,而他们的Drop实现也动态的释放借用。