Cell<T>

CellRefCell 在功能上没有区别,区别在于 Cell<T> 适用于 T 实现 Copy 的情况:

1
2
3
4
5
6
7
8
9
10
use std::cell::Cell;

fn main() {
let c = Cell::new("asdf");
let one = c.get();

c.set("qwer");
let two = c.get();
println!("{},{}", one, two);
}

输出:

1
asdf,qwer

因为实现了 Copy,所以 "asdf" 这个值被复制了一份,给了 one 变量.

Cell 没有可变引用
1
2
3
4
5
6
7
8
9
10
11
12
use std::cell::Cell;

fn main() {
// code snipet 1
let x = Cell::new(1);
let y = &x;
let z = &x;
x.set(2);
y.set(3);
z.set(4);
println!("{}", x.get());
}

这里,y, z 都是 x 的不可变引用,调用 y.set() 的时候,会自动 dereference 为

1
(&x).set() ===> x.set()

因此,y, z 调用 set() 都会修改 x 这个指针指向的数据,因此输出为 44

Cell::from_mut(), Cell::as_slice_of_cells() 使用技巧
  • Cell::from_mut(),该方法将 &mut T 转为 &Cell<T>
  • Cell::as_slice_of_cells(),该方法将 &Cell<[T]> 转为 &[Cell<T>]
1
2
3
4
5
6
7
8
9
10
11
12
13
// Wrong
fn is_even(i: i32) -> bool {
i % 2 == 0
}

fn retain_even(nums: &mut Vec<i32>) {
let mut i = 0;
for num in nums.iter().filter(|&num| is_even(*num)) {
nums[i] = *num;
i += 1;
}
nums.truncate(i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// True
use std::cell::Cell;

fn retain_even(nums: &mut Vec<i32>) {
let slice: &[Cell<i32>] = Cell::from_mut(&mut nums[..])
.as_slice_of_cells();

let mut i = 0;
for num in slice.iter().filter(|num| is_even(num.get())) {
slice[i].set(num.get());
i += 1;
}

nums.truncate(i);
}

这个例子里,我们希望通过 iter().filter() 遍历元素,此时迭代器是对元素的不可变借用,因此无法通过不可变借用来修改元素值(因为会违反 Rust 的借用规则)。而 Cell<T> 允许使用 .get().set() 方法,在不可变借用的上下文里修改值,从而绕过 Rust 的限制。所以我们需要的是 an immutable reference of a list of Cell<T>s.


RefCell<T>

RefCell<T> 解决的问题更进一步:解决可变引用、不可变引用共存的问题。本质只是将编译阶段的共存检查,推迟到运行时阶段,即如果运行时阶段里还是出现了 mutable borrow 和 immutable borrow 同时存在的情况下,依然会报运行时错误 Panic.

Rust 规则 智能指针带来的额外规则
一个数据只有一个所有者 Rc<T>/Arc<T> 让一个数据可以拥有多个所有者
要么多个不可变借用,要么一个可变借用 RefCell<T> 实现编译期可变、不可变引用共存
违背规则导致编译错误 违背规则导致运行时 panic

什么是内部可变性?

在某些场景中,一个值可以在其方法内部被修改,同时对于其它代码不可变。例如说考虑下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义在外部库中的特征
pub trait Messenger {
fn send(&self, msg: String);
}

// --------------------------
// 我们的代码中的数据结构和实现
struct MsgQueue {
msg_cache: Vec<String>,
}

impl Messenger for MsgQueue {
fn send(&self, msg: String) {
self.msg_cache.push(msg)
}
}

我们有个变量 x: MsgQueue,我们调用 x.send() 希望修改数据结构里的一部分,但是不希望 x 变量本身被修改。然而,send() 方法的签名是 &self,而且 .push() 修改了 x 的数据,因此 &self 会报错。

这个时候 RefCell<T> 就派上用场了。我们把 msg_cacheRefCell<Vec<String>> 包裹,就可以实现保持 x 不可变的情况下修改内部的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义在外部库中的特征
pub trait Messenger {
fn send(&self, msg: String);
}

// --------------------------
// 我们的代码中的数据结构和实现
struct MsgQueue {
msg_cache: Vec<String>,
}

impl Messenger for MsgQueue {
fn send(&self, msg: String) {
self.msg_cache.push(msg)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::cell::RefCell;
pub trait Messenger {
fn send(&self, msg: String);
}

pub struct MsgQueue {
msg_cache: RefCell<Vec<String>>,
}

impl Messenger for MsgQueue {
fn send(&self, msg: String) {
self.msg_cache.borrow_mut().push(msg)
}
}

fn main() {
let mq = MsgQueue {
msg_cache: RefCell::new(Vec::new()),
};
mq.send("hello, world".to_string());
}

注意这里的 borrow_mut()


Rc + RefCell

一个常见的用法是 Rc<RefCell<T>>,这样,多个 x = Rc<RefCell<T>> 可以共享底层的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
let s = Rc::new(RefCell::new("我很善变,还拥有多个主人".to_string()));

let s1 = s.clone();
let s2 = s.clone();
// let mut s2 = s.borrow_mut();
s2.borrow_mut().push_str(", oh yeah!");

println!("{:?}\n{:?}\n{:?}", s, s1, s2);
}

输出:

1
2
3
RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }
RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }
RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }