RefCell<T> 的一个常见用法是与 Rc<T> 结合。回忆一下 Rc<T> 允许对相同数据有多个所有者,不过只
能提供数据的不可变访问。如果有一个储存了 RefCell<T> 的 Rc<T> 的话,就可以得到有多个所有者 并
且可以修改的值了!
例如,回忆示例 15-18 的 cons list 的例子中使用 Rc<T> 使得多个列表共享另一个列表的所有权。因为
Rc<T> 只存放不可变值,所以一旦创建了这些列表值后就不能修改。让我们加入 RefCell<T> 来获得修改
列表中值的能力。示例 15-24 展示了通过在 Cons 定义中使用 RefCell<T>,我们就允许修改所有列表中
的值了:
文件名: src∕main.rs
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
示例 15-24:使用 Rc<RefCell<i32>> 创建可以修改的 List
这里创建了一个 Rc<RefCell<i32>> 实例并储存在变量 value 中以便之后直接访问。接着在 a 中用包含
value 的 Cons 成员创建了一个 List。需要克隆 value 以便 a 和 value 都能拥有其内部值 5 的所有权,而
不是将所有权从 value 移动到 a 或者让 a 借用 value。
我们将列表 a 封装进了 Rc<T> 这样当创建列表 b 和 c 时,他们都可以引用 a,正如示例 15-18 一样。
一旦创建了列表 a、b 和 c,我们将 value 的值加 10。为此对 value 调用了 borrow_mut,这里使用了第
五章讨论的自动解引用功能(”−> 运算符到哪去了?” 部分)来解引用 Rc<T> 以获取其内部的 RefCell<T>
值。borrow_mut 方法返回 RefMut<T> 智能指针,可以对其使用解引用运算符并修改其内部值。
当我们打印出 a、b 和 c 时,可以看到他们都拥有修改后的值 15 而不是 5:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.63s
Running `target/debug/cons-list`
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
这是非常巧妙的!通过使用 RefCell<T>,我们可以拥有一个表面上不可变的 List,不过可以使用
RefCell<T> 中提供内部可变性的方法来在需要时修改数据。RefCell<T> 的运行时借用规则检查也确实保
护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。
标准库中也有其他提供内部可变性的类型,比如 Cell<T>,它类似 RefCell<T> 但有一点除外:它并
非提供内部值的引用,而是把值拷贝进和拷贝出 Cell<T>。还有 Mutex<T>,其提供线程间安全的内
部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节关于这些不同类型之间的区别。