基本安全性约束
rust通过两种方式保证对象T的内存安全性:
1. 允许存在多个不可变的引用 &T 进行访问
2. 只允许一个可变的引用 &mut T 进行访问
并且1和2不能同时使用,要么使用多个不可变引用,要么只使用一个可变引用。
**所有者本身既可以是可变引用,也可以是不可变引用。
举个例子
let mut vec = vec![1, 2, 3];
let im_ref = &vec;
for i in 0..10 {
vec.push(i);
println!("now vec {:?}", im_ref);
}
编译器会报错,因为不可变引用im_ref在使用过程中进行了可变引用的访问(vec自身);这个在其他语言来看非常不可理喻,在rust中是安全性的基石。
虽然使用上障碍非常大,但是给予了引用强而可靠的安全性,即&T拿到的数据始终不变。
cell
下面说到标准库的cell模块,全名是std::cell
有时,我们需要多个可变的引用,cell就是为此而设计的一种对象容器。
cell模块提供了三种结构,Cell<T>、RefCell<T>和OnceCell<T>,注意这些容器都拥有了T的所有权,这三个容器的实现方式和使用场景有所区别,但效果都是使用对Cell的多个不可变引用&Cell,提供方法返回可修改的内部对象。即允许多个T可变的&Cell同时使用。
注意cell结构都不支持同步Sync特征,不支持多线程访问。并且显然,&Cell<T>也不再保证T的不可变性。
Cell<T>
通过所有权转移实现的Cell,该结构的方法一般会使用所有权移交的方式去提供可修改的T,当T实现了Copy时,get方法会进行数据拷贝(总之就是move的语义)
用于一些简单类型如数字。
RefCell<T>
以引用的形式,临时获取可变引用去提供可修改的T,同时间多个RefCell获取可变引用,在单线程中并不会出现“同时”出现多个实际的&mut T,如果同时出现,会直接触发运行时panic,当然这也违背了静态检查引用安全性的基本规则,所以引入RefCell实现可变性也是一种最后的手段。
OnceCell<T>
OnceCell是结合了Cell和RefCell的两种特点,允许所有权转移,也允许获取可变或不可变引用。
改写之前提到的例子如下:
let vec = vec![1, 2, 3];
let cell_vec = RefCell::new(vec);
let cell_vec_ref = &cell_vec;
for i in 0..10 {
cell_vec.borrow_mut().push(i);
println!("now vec {:?}", cell_vec_ref.borrow());
}
程序能够正常执行,注意RefCell的borrow_mut返回了可修改的内部可变引用,跳过了静态检查。(注意这里我没有将borrow_mut赋值到一个临时变量中,如果赋值了,一定要使用语句块将他及时释放,否则下次使用borrow或borrow_mut必定会导致运行时panic)