参考章节《Rust 程序设计语言》第15.4章 Rc<T> 引用计数智能指针
Rust 有一个叫做 Rc<T>
的类型。其名称为 引用计数(reference counting)
的缩写。
引用计数
意味着记录一个值引用的数量
来知晓这个值是否仍在被使用。如果某个值有0
个引用,就代表没有任何有效引用并可以被清理。
书上叫它引用计数智能指针
,我更喜欢把它叫做可克隆型智能指针
一想到可克隆
就能想到Rc::clone
,就能想到多有权
,就能想到引用计数
(PS:这只是我的叫法,大家可根据自己的理解随意发挥
)
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者
。
我们来看看下图

书上对这图的解释我也是醉了
列表 a
包含 5
之后是 10
,之后是另两个列表:b
从 3
开始而 c
从 4
开始。b
和 c
会接上包含 5
和 10
的列表 a
。
我晕,你直接说 b
和 c
包含 a
不就行了,你妹的废话一大堆
来看看这幅图对应的代码
1
2
3
4
5
6
7
8
9
10
11
12
|
enum List {
Cons(i32, Box<List>),
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));
}
|
这段代码并不能编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ 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
|
原因很简单,当创建 b
列表时,a
被移动进了 b
这样 b
就拥有了 a
。
接着当再次尝试使用 a
创建 c
时,这不被允许,因为 a
的所有权已经被移动。
那么如何解决这个问题
- 一种方法是改变
Cons
的定义来存放一个引用
,不过接着必须指定生命周期参数
。
通过指定生命周期参数,表明列表中的每一个元素都至少与列表本身存在的一样久。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
enum List<'a> {
Cons(i32, Box<&'a List<'a>>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let v = Cons(10, Box::new(&Nil));
let a = Cons(5, Box::new(&v));
let b = Cons(3, Box::new(&a));
let c = Cons(4, Box::new(&a));
}
|
- 第二种办法是使用
Rc<T>
智能指针
我们修改 List
的定义为使用 Rc<T>
代替 Box<T>
,当创建 a
时,Rc
的引用计数器为 1
1
2
3
4
5
6
7
8
9
10
11
12
13
|
enum List {
Cons(i32, Rc<List>), // 用 Rc<T> 代替 Box<T>
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc; // 需要使用 use 语句将 Rc<T> 引入作用域,因为它不在 prelude 中。
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); // 当创建 a 时,Rc 的引用计数器为 1
let b = Cons(3, Rc::clone(&a)); // 调用 Rc::clone(&a) 克隆 a 的所有权,这会将引用计数从 1 增加到 2
let c = Cons(4, Rc::clone(&a)); // 调用 Rc::clone(&a) 克隆 a 的所有权,这会将引用计数从 2 增加为 3
}
|
当创建 b
时会克隆 a
,这会将引用计数从 1
增加到 2
并允许 a
和 b
共享 Rc<List>
中数据的所有权
。
当创建 c
时会克隆 a
,这会将引用计数从 2
增加为 3
并允许 a
和 c
共享 Rc<List>
中数据的所有权
。
当调用 Rc::clone
时,Rc<List>
中数据的引用计数都会增加
,直到有0
个引用之前其数据都不会被清理
。
Rc<T>
允许我们克隆所有权
- 调用
Rc::clone
时会增加 引用计数器