前面我们提到,Rust 通过其所有权机制,严格控制拥有和借用关系,来保证程序的安全,并且这种安全是在编译期可计算、可预测的。但是这种严格的控制,有时也会带来灵活性的丧失,有的场景下甚至还满足不了需求。
因此,Rust 标准库中,设计了这样一个系统的组件:Cell
, RefCell
,它们弥补了 Rust 所有权机制在灵活性上和某些场景下的不足。同时,又没有打破 Rust 的核心设计。它们的出现,使得 Rust 革命性的语言理论设计更加完整,更加实用。
具体是因为,它们提供了 内部可变性
(相对于标准的 继承可变性
来讲的)。
通常,我们要修改一个对象,必须
- 成为它的拥有者,并且声明
mut
; - 或 以
&mut
的形式,借用;
而通过 Cell
, RefCell
,我们可以在需要的时候,就可以修改里面的对象。而不受编译期静态借用规则束缚。
Cell
Cell
有如下特点:
Cell<T>
只能用于T
实现了Copy
的情况;
.get()
.get()
方法,返回内部值的一个拷贝。比如:
use std::cell::Cell;
let c = Cell::new(5);
let five = c.get();
.set()
.set()
方法,更新值。
use std::cell::Cell;
let c = Cell::new(5);
c.set(10);
RefCell
相对于 Cell
只能包裹实现了 Copy
的类型,RefCell
用于更普遍的情况(其它情况都用 RefCell
)。
相对于标准情况的 静态借用
,RefCell
实现了 运行时借用
,这个借用是临时的。这意味着,编译器对 RefCell
中的内容,不会做静态借用检查,也意味着,出了什么问题,用户自己负责。
RefCell
的特点:
- 在不确定一个对象是否实现了
Copy
时,直接选RefCell
; - 如果被包裹对象,同时被可变借用了两次,则会导致线程崩溃。所以需要用户自行判断;
RefCell
只能用于线程内部,不能跨线程;RefCell
常常与Rc
配合使用(都是单线程内部使用);
我们来看实例:
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
shared_map.borrow_mut().insert("africa", 92388);
shared_map.borrow_mut().insert("kyoto", 11837);
shared_map.borrow_mut().insert("piccadilly", 11826);
shared_map.borrow_mut().insert("marbles", 38);
}
从上例可看出,用了 RefCell
后,外面是 不可变引用
的情况,一样地可以修改被包裹的对象。
常用方法
.borrow()
不可变借用被包裹值。同时可存在多个不可变借用。
比如:
use std::cell::RefCell;
let c = RefCell::new(5);
let borrowed_five = c.borrow();
let borrowed_five2 = c.borrow();
下面的例子会崩溃:
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow_mut();
let b = c.borrow(); // this causes a panic
}).join();
assert!(result.is_err());
.borrow_mut()
可变借用被包裹值。同时只能有一个可变借用。
比如:
use std::cell::RefCell;
let c = RefCell::new(5);
let borrowed_five = c.borrow_mut();
下面的例子会崩溃:
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow();
let b = c.borrow_mut(); // this causes a panic
}).join();
assert!(result.is_err());
.into_inner()
取出包裹值。
use std::cell::RefCell;
let c = RefCell::new(5);
let five = c.into_inner();
一个综合示例
下面这个示例,表述的是如何实现两个对象的循环引用。综合演示了 Rc
, Weak
, RefCell
的用法