Rust从入门到实战系列一百七十五:Rc<T> 引用计数智能指针

ch15-04-rc.md commit 45fe0fc9af98a214ed779d2cfac6773bdbfc708e
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能
会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有
指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理。
为了启用多所有权,Rust 有一个叫做 Rc 的类型。其名称为 引用计数(reference counting)的缩写。
引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表
没有任何有效引用并可以被清理。
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最
后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,
正在看电视的人肯定会抓狂的!
Rc 用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的哪一
部分会最后结束使用它的时候。如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的
所有者,正常的所有权规则就可以在编译时生效。
注意 Rc 只能用于单线程场景;第十六章并发会涉及到如何在多线程程序中进行引用计数。
使用 Rc 共享数据
让我们回到示例 15-5 中使用 Box 定义 cons list 的例子。这一次,我们希望创建两个共享第三个列
表所有权的列表,其概念将会看起来如图 15-3 所示:
两个列表, b 和 c, 共享第三个列表 a 的所有权
列表 a 包含 5 之后是 10,之后是另两个列表:b 从 3 开始而 c 从 4 开始。b 和 c 会接上包含 5 和 10 的
列表 a。换句话说,这两个列表会尝试共享第一个列表所包含的 5 和 10。
尝试使用 Box 定义的 List 实现并不能工作,如示例 15-17 所示:
文件名: src∕main.rs
enum List {
Cons(i32, Box),
Nil,
}
use crate::List::{Cons, 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));
}
示例 15-17: 展示不能用两个 Box 的列表尝试共享第三个列表的所有权
编译会得出如下错误:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0382]: use of moved value: a
–> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because a has type List, which does not implement the Copy trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
For more information about this error, try rustc --explain E0382.
error: could not compile cons-list due to previous error
Cons 成员拥有其储存的数据,所以当创建 b 列表时,a 被移动进了 b 这样 b 就拥有了 a。接着当再次尝
试使用 a 创建 c 时,这不被允许,因为 a 的所有权已经被移动。
可以改变 Cons 的定义来存放一个引用,不过接着必须指定生命周期参数。通过指定生命周期参数,表
明列表中的每一个元素都至少与列表本身存在的一样久。这是示例 15-17 中元素与列表的情况,但并不
是所有情况都如此。
相反,我们修改 List 的定义为使用 Rc 代替 Box,如列表 15-18 所示。现在每一个 Cons 变量都
包含一个值和一个指向 List 的 Rc。当创建 b 时,不同于获取 a 的所有权,这里会克隆 a 所包含的
Rc,这会将引用计数从 1 增加到 2 并允许 a 和 b 共享 Rc 中数据的所有权。创建 c 时也会克
隆 a,这会将引用计数从 2 增加为 3。每次调用 Rc::clone,Rc 中数据的引用计数都会增加,直到
有零个引用之前其数据都不会被清理。
文件名: src∕main.rs
enum List {
Cons(i32, Rc),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
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));
}
示例 15-18: 使用 Rc 定义的 List
需要使用 use 语句将 Rc 引入作用域,因为它不在 prelude 中。在 main 中创建了存放 5 和 10 的列
表并将其存放在 a 的新的 Rc 中。接着当创建 b 和 c 时,调用 Rc::clone 函数并传递 a 中 Rc
的引用作为参数。
也可以调用 a.clone() 而不是 Rc::clone(&a),不过在这里 Rust 的习惯是使用 Rc::clone。Rc::clone 的
实现并不像大部分类型的 clone 实现那样对所有数据进行深拷贝。Rc::clone 只会增加引用计数,这并不
会花费多少时间。深拷贝可能会花费很长时间。通过使用 Rc::clone 进行引用计数,可以明显的区别深
拷贝类的克隆和增加引用计数类的克隆。当查找代码中的性能问题时,只需考虑深拷贝类的克隆而无需
考虑 Rc::clone 调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值