Rust 从 0 到 1- 智能指针 -Rc<T>
大部分情况下所有权是非常明晰的:我们可以清楚的知道哪个变量拥有数据的所有权。但是,也存在拥有多个所有者的情况。譬如,在图数据结构中,多个边可能指向相同的结点,我们在某些场景下可以理解为这个结点被所有指向它的边所拥有,直到没有任何边指向它的时候才可以被清理。
为了可以使数据具有多所有权,Rust 提供了 Rc<T> 类型,即引用计数(reference counting)的缩写。Rc<T> 会记录一个数据引用的数量,如果某个数据的引用数为 0,即不存在任何有效引用了,那么就可以被清理掉了。
我们可以把 Rc<T> 想象为房间中的电视。当第一个人进来看电视时,他把电视打开;后面其他人也可以进来看电视,而当最后一个人离开房间时,因为没人在看电视了,他把电视关掉。如果有人在其他人还在看的时候就关掉了电视,其他人肯定会有意见!
Rc<T> 用于当我们希望存储在堆上的数据可以在程序中的多处进行读取,但是无法在编译时确定谁会最后结束使用它的场景。如果可以确切知道谁是最后一个使用者的话,其就可以做为数据的所有者,那么所有权规则就可以正常发生作用了。
注意 Rc<T> 只能用于单线程场景;后面介绍并发的时候会讨论如何在多线程场景下进行引用计数。
使用 Rc<T> 共享数据
让我们回到前面使用 Box<T> 实现 cons list 的例子。假设,我们希望创建两个列表,他们共享另外一个列表的所有权,看起来如下图所示(来自官网):
列表 b 从 3 开始,列表 c 从 4 开始。它们共享包含 5 和 10 的列表 a。如果我们尝试使用 Box<T> 去实现,将无法编译通过,参考下面的例子:
如果尝试进行编译,我们会得到类似下面的错误:
Cons 会获取数据的所有权,所以当创建列表 b 时,a 的所有权被转移给了 b 。因此,当我们创建 c 时,无法再获得 a 的所有权,因为其所有权已经转移了。
我们可以改变 Cons 定义中的类型为引用,但是这样的话我们必须指定生命周期参数。通过生命周期参数,指定列表中的每一个元素都至少需要与整个列表存在的一样长。借用检查器不会让 let a = Cons(10, &Nil); 编译通过,因为 Nil 在 a 获取其引用之前就已经被丢弃了。在这种情况下,我们可以使用 Rc<T> 代替 Box<T>,参考下面的例子:
上面的例子中,每一个 Cons 类型的数据都包含一个 i32 类型的值和一个包含 List 类型值的 Rc 类型值。当创建列表 b 时,我们会克隆变量 a ,这时引用计数会加 1(从 1 变为 2)并允许 a 和 b 共享 Rc 中数据的所有权。创建列表 c 时又克隆了变量 a,这时引用计数又加 1(从 2 变为 3)。即,每次调用 Rc::clone ,Rc<List> 中数据的引用计数都会增加,并且在引用计数变为 0 之前数据都不会被清理。
我们也可以使用 a.clone() 进行克隆操作,但是 Rust 中的习惯用法是 Rc::clone。Rc::clone 并不像大多数类型的 clone 方法那样实现了对数据的深度拷贝。Rc::clone 只会增加引用计数,这不会消耗多少时间,而深度拷贝可能需要花费比较长的时间。使用 Rc::clone 进行引用计数,可以将深度拷贝的行为和增加引用计数的行为明显的区分出来。这在我们排查代码中的性能问题时,可以将引用计数类的克隆排除,而只需关注深度拷贝类的克隆。
通过克隆 Rc<T> 增加引用计数
让我们对前面的例子做一些修改,以便于观察引用计数的变化。并将列表 c 置于内部作用域中,这样就可以观察到当列表 c 离开作用域时引用计数的变化。参考下面的例子:
在上面的例子中,我们通过函数 Rc::strong_count 得到当前引用计数的值,并打印出来。这个函数之所以叫做 strong_count 而不是 count 是因为 Rc<T> 还有一个 weak_count 函数;在后面讨论避免循环引用的章节我们会介绍 weak_count 的用途。尝试运行上面的例子,我们会得到类似下面的结果:
从结果中我们可以看到 Rc<List> 的初始引用计数为 1,后面每次调用 Rc::clone,计数都会增加 1。当 c 离开作用域时,计数减 1。我们需要主动调用一个函数来减少计数;Rc<T> 实现的 Drop trait 在其实例离开作用域时自动减少引用计数。在结果中我们并未展示出来的是:在 main 函数的结尾,当 b 和 a 相继离开作用域时,计数就会变为 0,这时 Rc 就会被清理掉。
通过不可变引用, Rc<T> 允许在程序中多处只读地共享数据。如果 Rc<T> 也允许可变引用,就可能会违反借用规则:同一处的多个可变引用可能造成数据竞争和不一致。但不可避免的在有些场景下我们需要修改数据!下面我们将讨论在这种场景下要如何处理。
版权声明: 本文为 InfoQ 作者【山】的原创文章。
原文链接:【http://xie.infoq.cn/article/d986bcc30244decac67b85b07】。文章转载请联系作者。
评论