Cell和RefCell代表了Rust的一个特性:Interior Mutability,内在的可变性。
顾名思义,Cell就是一个细胞,一个单元,或者说一个容器。这个容器和Box不同,Box是一个不可变的指针,如果我们想改变一个box的值,比如这么搞:
fn main() { let mut x = Box::new(10i32); *x = 20; println!("{:?}", x); }
结果会打印出来20,我们无法在不dereference的情况下修改Box里面的值,并且如果要修改的话,必须要用let mut来定义Box的绑定变量。
而使用let mut有很多不方便,因为Rust的一条铁律就是可变修改只能有一个,没有还回来之前根本不能继续修改。所以cell和refcell的存在就有了必要,我们先看cell的特点:
use std::cell::Cell; fn main() { let x = Cell::new(20i32); let y = &x; let z = &x; y.set(40i32); println!("{:?}", x); z.set(80i32); println!("{:?}", x); }
打印出来的结果是:
Cell { value: 40 } Cell { value: 80 }
是不是很神奇?y和z都是x的不可变调用,但是都成功的修改了x的值。这就是Rust在不破坏其核心安全性的前提下,对易用性作出的妥协,让我们可以在在同一个scope里面,让一个变量可以被一个或者数个不可变借用修改。
这个其实有点类似python的tuple内置list的情况,我们知道python里面tuple是immutable的,list是mutable的,于是我们可以有这样的代码:
x = ([1, 2], [3, 4]) x[0].append(5) print(x)
我们得到了([1,2,5], [3,4]). 虽然实现机理上Rust和Python区别很大,但是interior mutability的意思,基本上就是上面这个List的tuple所描述的。
Rust的Cell是很方便,但是问题在于不是每个类型都可以用Cell包起来。Rust的文档说的明白,只有实现了copy属性的类型才能够用cell包裹,一般来说,我们自己定义的struct和trait都是没有copy属性的,所以这种就只能用RefCell。
顾名思义,RefCell是引用单元。这个的限制就更多了:没有了威武霸气的set方法,取而代之的是borrow和borrow_mut,看名字也看得出来就是可读借用和可变借用。注意这里rust的文档特别强调了,依然需要满足可变借用的唯一性,甚至更加严格:如果存在可变借用,那么在该可变借用的作用域结束之前,那么即便是再次进行只读借用,编译器都会报错,比如下面这个代码就无法通过编译:
use std::cell::RefCell; fn main() { let x = RefCell::new(20i32); let y = x.borrow_mut(); let z = x.borrow(); }
看到这里可能会非常灰心失望,这么严格的限制,要RefCell何用?当然是有用的,因为RefCell的限制只是针对一个变量的,我们可以非常轻易的绕过这个限制:
use std::cell::RefCell; fn main() { let x = RefCell::new(20i32); let z = &x; let y = &x; *z.borrow_mut() = 10; println!("{:?}", x); *y.borrow_mut() = 30; println!("{:?}", x); }
系统输出
RefCell { value: 10 } RefCell { value: 30 }
所以我们可以通过其他的只读绑定的borrow_mut来修改值,然后用最初的Refcell负责输出。当然如果要抬杠,说我就是想用一个变量,同时存在多个mutable的borrow怎么办?Rust自然也有办法,在下一节rc里面会继续说这个问题.