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>
这样的类型呢?回忆一下第四章所学的借用规则:
- 在任意给定时间,只能拥有如下中的一个:
- 一个可变引用。
- 任意属性的不可变引用。
- 引用必须总是有效的。
对于引用和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>
,我们使用borrow
和borrow_mut
方法,它是RefCell<T>
拥有的安全 API 的一部分。borrow
返回Ref
类型的智能指针,而borrow_mut
返回RefMut
类型的智能指针。这两个类型实现了Deref
所以可以被当作常规引用处理。Ref
和RefMut
动态的借用所有权,而他们的Drop
实现也动态的释放借用。