RUST
提供了一个名为Rc<T>
的类型来支持多重所有权,Rc
是Reference counting
的缩写。Rc<T>
类型实例会在内部维护一个用于记录值引用次数的计数器,从而确定这个值是否仍在使用。如果一个值的引用次数为零,就意味着这个值可以被安全清理掉,而不会触发引用失效的问题。
你可以把Rc<T>
想象成客厅的电视,在第一个人打开电视之后,后续进入客厅的其他人都可以直接观看电视。电视会一直保持开启状态直到最后一个人离开时关闭,因为此时不需要再使用电视了。
当你希望将堆上的一些数据分享给程序的多个部分同时使用,而又无法在编译器确定哪个部分会最后释放这些数据时,我们就可以使用Rc<T>
类型。
需要注意的是,Rc<T>
只能被用于单线程场景中。
Rc<T>
共享数据基于Box<T>
无法实现下面的场景,Cons
变体有持有它存储的数据,因此,整个a
列表所有权会在创建b
列表时被移动到b
中。换句话说,b
持有了a
列表的所有权。当我们随后再次使用a
来创建c
列表时就会出现编译错误。
use crate::List::{Cons, Nil};
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
我们当然可以改变Cons
的定义来让它持有一个引用而不是所有权,并为其指定对应的生命周期参数。但这个生命周期参数会要求列表中所有元素的存活时间都至少和列表本身一样长。换句话说,借用检查器最终会阻止我们编译类似let a = Cons(10, &Nil);
这样的代码,因为此处临时创建的Nil
变体值会在a
取得其引用前被丢弃。
另外一种解决方案是,我们将List
中的Box<T>
修改为Rc<T>
,我们只需要在创建b
的过程中克隆a
的Rc<List>
智能指针即可,而不再需要获取a
的所有权。这会使a
和b
共享Rc<List>
数据的所有权,并使智能指针中的引用计数从1增加到2。
use crate::List::{Cons, Nil};
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
由于Rc<T>
没有被包含在预导入模块中,这里使用use
语句将它引入作用域。代码中也可以使用a.clone()
代替Rc::clone(&a)
来实现同样的效果,但RUST
惯例是在此场景下使用Rc::clone
,因为Rc::clone
不会执行数据的深度拷贝操作,调用Rc::clone
只会增加引用技术,而不会有太多时间消耗。
拿Go
语言中的Slice
来说,其实也类似于这里的Rc
。每次对Slice
做重新赋值,其实是对SliceHeader
做了一次克隆操作,两个Slice
的底层数据仍然是共享的。
为什么Box<T>
不可以通过clone
实现上述的功能呢?因为Box<T>
没有实现Clone trait
,需要我们自己去实现clone
的能力。
因篇幅问题不能全部显示,请点此查看更多更全内容