搜索
您的当前位置:首页正文

引用计数 Rc 数据类型

来源:易榕旅网

RUST提供了一个名为Rc<T>的类型来支持多重所有权,RcReference 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的过程中克隆aRc<List>智能指针即可,而不再需要获取a的所有权。这会使ab共享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的能力。

因篇幅问题不能全部显示,请点此查看更多更全内容

Top