@@ -1,31 +1,30 @@
# `Rc<T>`,引用计数的灵巧指针
在大多数情况下,所有权是明确的:咱们确切地知道哪个变量拥有给定值。然而,在有些情况下, 单个值可能有多个所有者。例如,在图数据结构中 ,多条边可能指向同一个 节点,而该 节点在概念上被 所有指向他的边所拥有。一个 节点不应该被清理,除非没有任何边指向他, 因此没有了所有者。
在大多数情况下,所有权是明确的:咱们确切地知道哪个变量拥有给定值。然而,存 在单个值可能有多个所有者的情形 。例如,在图数据结构下 ,多条边可能指向同一节点,进而这个 节点在概念上由 所有指向他的边所拥有。节点不应该被清理,除非没有任何边指向他而 因此没有了所有者。
咱们必须使用 Rust 类型 `Rc<T>` 显式 启用多重所有权,`Rc<T>` 是 * 引用计数, reference counting * 的缩写。`Rc<T>` 类型记录了对一个 值的引用数量,以确定该值是否仍在使用。如果对某个 值的引用为零,那么这个值就可以 被清理掉 ,而不会有任何引用变得无效。
咱们必须显式地通过 使用 Rust 类型 `Rc<T>` 启用多重所有权,`Rc<T>` 是 * 引用计数, reference counting * 的缩写。`Rc<T>` 类型会跟踪对 值的引用数量,以确定该值是否仍在使用。当对 值的引用为零时,则该值可 被清理,而不会有没有 任何引用变得无效。
请将 `Rc<T>` 设想为客厅里的一台 电视。在有 人进来看电视时,他们 会打开他 。其他人可以进入客厅并观 看电视。当最后一人离开客厅时,他们 会关掉电视,因为他已 不再被使用了。如果 有人在其他人还在看电视的情况下 关掉电视,剩下的看 电视的人 就会哗然!
请将 `Rc<T>` 设想为客厅里的电视。当一个 人进来看电视时,就 会打开电视 。其他人可以进入客厅看电视。当最后一个 人离开客厅时,就 会关掉电视,因为电视 不再被使用。当 有人在其他人还在看电视时 关掉电视,剩下的电视观众 就会哗然!
当我们想在内存 堆上分配一些 数据给 程序的多个部分读取,且无法在编译时确定哪个部分会 最后使用完这些 数据时,我们就会 使用 `Rc<T>` 类型。如果 我们知道哪个部分会最后完成,我们 就可以让那个部分成为数据的所有者,而在 编译时执行的正常所有权规则就会生效。
当我们打算在 堆上分配某 一数据供 程序的多个部分读取,且无法在编译时确定哪个部分最后结束使用该 数据时,我们就要 使用 `Rc<T>` 类型。若 我们知道哪个部分会最后结束使用, 就可以直接 让那个部分成为该 数据的所有者,并且 编译时执行的正常所有权规则就会生效。
请注意 `Rc<T>` 仅适用于单线程场景, only for use in single-threaded scenarios 。当咱 们在第 16 章中讨论并发时,咱 们将介绍如何 在多线程程序中进行 引用计数。
请注意, `Rc<T>` 仅适用于单线程的 场景。当我 们在第 16 章中讨论并发时,我 们将介绍怎样 在多线程的 程序中实现 引用计数。
## 使用 `Rc<T>` 来 共用数据
## 共用数据
**Using `Rc<T>` to Share Data **
让我们回到清单 15-5 中咱们的构造列表示例。回想一下,我们用 `Box<T>` 定义了他。这一次,我们将创建两个列表,二者会公用第三个列表的所有权。从概念上看,这类似于下图 15-3:
我们来回到 [清单 15-5 ](./box-t.md#listing_15-5 ) 中的构造列表示例。回想一下,我们使用 `Box<T>` 定义了他。这一次,我们将创建两个列表,他们共用第三个列表的所有权。从概念上讲,这类似于下图 15-3。
<a name="f_15-3"></a>

* 图 15-03: 两个列表`b` 与 `c`,共用第三个列表 `a` 的所有权*
** 图 15-03** :两个列表 `b` 与 `c` ,共用第三个列表 `a` 的所有权
咱 们将创建一个 包含 `5` 和 `10` 的列表。然后我们再做 两个列表:以 `3` 开头的 `b` 和以 `4` 开头的 `c` , 然后 `b` 和 `c` 的 列表都会延续到第一个 包含 `5` 和 `10` 的 `a` 列表 。换句话说,这两个列表将共用第一个 包含 `5` 和 `10` 的列表。
我 们将创建包含 `5` 和 `10` 的列表 `a` 。然后, 我们将构造 两个列表:以 `3` 开头的 `b` 和以 `4` 开头的 `c` 。 然后 `b` 和 `c` 两个 列表都将接续到 包含 `5` 和 `10` 的第一个列表 `a` 。换句话说,这两个列表将共用包含 `5` 和 `10` 的第一个 列表。
尝试使用带有 `Box<T>` 的 `List` 定义来实现这个 场景是行不通的,如示例 15-17 所示:
如清单 15-17 所示, 尝试使用我们 带有 `Box<T>` 的 `List` 定义来实现这一 场景是行不通的。
<a name="listing_15-17"></a>
文件名:`src/main.rs`
``` rust
@@ -43,98 +42,118 @@ fn main() {
}
```
* 清单 15-17: 演示咱 们不允许有 两个使用 `Box<T>` 的列表,试图共用第三个列表的所有权*
** 清单 15-17** :演示我 们不允许让 两个使用 `Box<T>` 的列表,试图共用第三个列表的所有权
当我们编译这段代码时,我们得到了这样的 报错:
当我们编译这段代码时,我们会 得到下面这个 报错:
``` console
$ cargo run
Compiling sp_demos v0.1.0 (/home/peng /rust-lang/sp_demos )
Compiling cons-list v0.1.0 (/home/hector /rust-lang-zh_CN/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))));
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
|
note: if `List` implemented `Clone`, you could clone the value
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^ consider implementing `Clone` for this type
...
10 | let b = Cons(3, Box::new(a));
| - you could clone this value
For more information about this error, try `rustc --explain E0382`.
error: could not compile `sp_demos` due to previous error;
error: could not compile `cons-list` (bin "cons-list") due to 1 previous error
```
`Cons` 变种拥有他们持有 的数据,所以 当我们创建 `b` 列表 时,`a` 被迁移到 `b` 中, `b` 拥有了 `a` 。然后,当我们在创建 `c` 时试图再次使用 `a` ,我们不被允许,因为 `a` 已经 被迁移了 。
`Cons` 变种拥有他们包含 的数据,因此 当我们创建列表 `b` 时,`a` 会 被迁移到 `b` 中进而 `b` 拥有 `a` 。然后,当我们在创建 `c` 时试图再次使用 `a` 时 ,我们不被允许,因为 `a` 已被迁移。
咱 们原本可以将 `Cons` 的定义修改为持有引用,但那样咱们就 必须指定生命周期参数。通过指定生命周期参数,咱们将 指定列表中的每个元素,都至少与 整个列表的寿命一样长。清单 15-17 中的元素与列表就是这种情况,但并非在所有情况下都如此。
我 们原本可以修改 `Cons` 的定义为包含引用,但随后我们将 必须指定生命周期参数。通过指定生命周期参数,我们实际上是在 指定列表中的每个元素,都将 至少存活 整个列表的寿命一样长。清单 15-17 中的元素与列表就是这种情况,但并非在所有情况下都如此。
相反,我们将改变 `List` 的定义,使用 `Rc<T>` 来代替 `Box <T>` ,如下清单 15-18 所示。现在每个 `Cons` 变种将持有 一个值和一个指向 `List` 的 `Rc<T>` 。当我们创建 `b` 时,我们将克隆 `a` 所持有 的 `Rc<List>` ,而不是取得 `a` 的所有权,从而将引用的数量 从一个 增加到两个 ,并让 `a` 和 `b` 共用该 `Rc<List>` 中数据的所有权。在创建 `c` 时,我们也将克隆 `a` ,将引用的数量从两个 增加到三个 。每次我们调用 `Rc::clone` , `Rc<List>` 中 数据的引用数就会 增加,除非对他的引用为零,否则数据不会被清理掉 。
相反,我们将修 改 `List` 的定义为在 `Box<T>` 处使用 `Rc <T>` ,如下清单 15-18 所示。现在每个 `Cons` 变种将包含 一个值和一个指向 `List` 的 `Rc<T>` 。当我们创建 `b` 时,我们将克隆 `a` 包含 的 `Rc<List>` ,而不是取得 `a` 的所有权,从而将引用计数 从一增加到而 ,并让 `a` 和 `b` 共用该 `Rc<List>` 中的 数据的所有权。在创建 `c` 时,我们也将克隆 `a` ,将引用计数从二 增加到三。每次我们调用 `Rc::clone` 时,到 `Rc<List>` 内 数据的引用计数都将 增加,并且 除非对他的引用为零,否则数据不会被清理。
<a name="listing_15-18"></a>
文件名:`src/main.rs`
``` rust
{ { #include .. / .. / projects / rc_demo / src / main . rs } }
```
enum List {
Cons ( i32 , Rc < List > ) ,
Nil ,
}
* 清单 15-18: 使用 `Rc<T>` 的 `List` 定义 *
use crate ::List ::{ Cons , Nil } ;
use std ::rc ::Rc ;
我们需要添加一个 `use` 语句来将 `Rc<T>` 引入作用域,因为他不在 Rust 前奏中。在 `main` 中,我们创建了包含 `5` 和 `10` 的列表,并将其存储在 `a` 中的新 `Rc<List>` 中。然后当我们创建 `b` 和 `c` 时,我们调用了 `Rc::clone` 函数,并将对 `a` 中的 `Rc<List>` 的引用作为参数传递。
我们本可以调用 `a.clone()` 而不是 `Rc::clone(&a)` ,但是 Rust 的惯例是在这种情况下使用 `Rc::clone` 。`Rc::clone` 的实现并不像大多数类型的 `clone` 实现那样对所有数据进行深度拷贝。对 `Rc::clone` 的调用只是增加引用计数,这不会花费很多时间。数据的深度拷贝会花费很多时间。通过使用 `Rc::clone` 进行引用计数,我们可以直观地区分出深拷贝的那种克隆和增加引用计数的那种克隆。当寻找代码中的性能问题时,我们只需要考虑深拷贝的克隆,而可以不考虑对 `Rc::clone` 的调用。
> 注:第 4 章 [变量与数据交互方式之二:克隆](Ch04_Understanding_Ownership.md#变量与数据交互方式之二克隆) 中,曾提到:“当咱们看到对 `clone` 的调用时,咱们就知道一些任意的代码正在被执行,而这些代码可能开销很大。这是表明正在发生一些不同寻常事情的明显标志。”
## 克隆 `Rc<T>` 会增加引用计数
**Cloning an `Rc<T>` Increases the Reference Count **
我们来修改示例 15-18 中的工作示例,以便我们可以看到在我们创建和删除对 `a` 中的 `Rc<List>` 的引用时,引用计数会发生变化。
在下面清单 15-19 中,我们将更改 `main` ,使其具有围绕列表 `c` 的内部作用域;然后我们可以看到当 `c` 超出作用域时引用计数如何变化。
文件名:`src/main.rs`
``` rust
fn main ( ) {
let a = Rc ::new ( Cons ( 5 , Rc ::new ( Cons ( 10 , Rc ::new ( Nil ) ) ) ) ) ;
println! ( " 在创建出 a 后,引用计数为 {} " , Rc ::strong_count ( & a ) ) ;
let b = Cons ( 3 , Rc ::clone ( & a ) ) ;
println! ( " 在创建出 b 后,引用计数为 {} " , Rc ::strong_count ( & a ) ) ;
{
let c = Cons ( 4 , Rc ::clone ( & a ) ) ;
println! ( " 在创建出 c 后,引用计数为 {} " , Rc ::strong_count ( & a ) ) ;
}
println! ( " 在 c 超出作用域后,引用计数为 {} " , Rc ::strong_count ( & a ) ) ;
let c = Cons ( 4 , Rc ::clone ( & a ) ) ;
}
```
* 清单 15-19: 打印出引用计数 *
** 清单 15-18 ** :使用 `Rc<T>` 的 `List` 定义
在程序中引用计数发生变化的每一点上,我们都会打印引用计数,我们通过调用 `Rc::strong_count` 函数得到这个计数。这个函数被命名为 `strong_count` 而不是 `count` ,是因为 `Rc<T>` 类型也有一个 `weak_count` ;我们将在 [“防止引用循环:将 `Rc<T>` 变成 `Weak<T>`” ](#防止引用循环将-rct-转变为-weakt ) 小节中看到 `weak_count ` 的用途 。
我们需要添加一个 `use` 语句来带入 `Rc<T>` 到作用域,因为他不在前奏中。在 `main` 中,我们创建了包含 `5` 和 `10` 的列表并存储在 `a` 中的新 `Rc<List>` 中。然后,当我们创建 `b` 和 `c` 时,我们调用 `Rc::clone` 函数,并作为参数传递对 `a` 中的 `Rc<List> ` 的引 用。
这段代码打印出以下内容:
我们本可以调用 `a.clone()` 而不是 `Rc::clone(&a)` ,但是 Rust 的约定是在这种情况下要使用 `Rc::clone` 。`Rc::clone` 的实现并不像大多数类型的 `clone` 实现那样,会构造所有数据的深度拷贝。对 `Rc::clone` 的调用只会增加引用计数,这不会花费太多时间。数据的深度拷贝会花费很多时间。通过使用 `Rc::clone` 进行引用计数,我们可以直观地区分深拷贝的克隆类别和增加引用计数的克隆类别。在查找代码中的性能问题时,我们只需考虑深拷贝的克隆,而可以忽略对 `Rc::clone` 的调用。
> **译注**:第 4 章的 [变量与数据相互作用:克隆](../ownership/about_ownership.md#变量与数据相互作用克隆) 小节中,我们曾提到:“当咱们看到对 clone 的调用时,咱们就知道一些任意代码正被执行,而这些代码可能开销高昂。这是一些不同寻常事情正在发生的直观指示器。”
## 通过克隆增加引用计数
我们来修改示例 15-18 中的工作的示例,以便可以看到在创建和删除对 `a` 中的 `Rc<List>` 的引用时,引用计数的变化。
在下面清单 15-19 中,我们将更改 `main` ,使其具有一个围绕列表 `c` 的内层作用域;然后,我们可以看到当 `c` 超出作用域时,引用计数如何变化。
<a name="listing_15-19"></a>
文件名:`src/main.rs`
``` rust
- - 跳 过 代 码 - -
fn main ( ) {
let a = Rc ::new ( Cons ( 5 , Rc ::new ( Cons ( 10 , Rc ::new ( Nil ) ) ) ) ) ;
println! ( " 创建 a 后的计数 = {} " , Rc ::strong_count ( & a ) ) ;
let b = Cons ( 3 , Rc ::clone ( & a ) ) ;
println! ( " 创建 b 后的计数 = {} " , Rc ::strong_count ( & a ) ) ;
{
let c = Cons ( 4 , Rc ::clone ( & a ) ) ;
println! ( " 创建 c 后的计数 = {} " , Rc ::strong_count ( & a ) ) ;
}
println! ( " c 超出作用域后的计数 = {} " , Rc ::strong_count ( & a ) ) ;
}
```
**清单 15-19 ** :打印引用计数
在程序中引用计数变化的每个点位,我们都打印引用计数,我们通过调用 `Rc::strong_count` 函数得到引用计数。这个函数之所以名为 `strong_count` 而不是 `count` ,是因为 `Rc<T>` 类型还有个 `weak_count` ;我们将在 [使用 `Weak<T>` 防止引用循环 ](./ref-cycles.md#使用-weakt-防止引用循环 ) 小节中看到 `weak_count` 的用途。
这段代码会打印以下内容:
``` console
$ cargo run
Compiling sp _demos v0.1.0 (/home/lennyp /rust-lang/sp _demos )
Finished dev [unoptimized + debuginfo] target(s) in 0.40 s
Running `target/debug/sp _demos `
在 创建出 a 后,引用计数为 1
在 创建出 b 后,引用计数为 2
在 创建出 c 后,引用计数为 3
在 c 超出作用域后,引用计数为 2
Compiling rc _demo v0.1.0 (/home/hector /rust-lang-zh_CN/projects/rc _demo)
Finished ` dev` profile [unoptimized + debuginfo] target(s) in 0.17 s
Running `target/debug/rc _demo`
创建 a 后的计数 = 1
创建 b 后的计数 = 2
创建 c 后的计数 = 3
c 超出作用域后的计数 = 2
```
我们可以看到 `a` 中的 `Rc<List>` 的初始引用计数为 `1` ;然后每次我们调用 `clone` ,计数都会增加 `1` 。当 `c` 超出作用域时,计数会减少 `1` 。我们不必像调用 `Rc::clone` 增加引用计数那样调用一个 函数来减少引用计数:当 `Rc<T>` 值超出作用域时,`Drop` 特质的实现会 自动减少引用计数。
我们可以看到, `a` 中的 `Rc<List>` 有着 1 的初始引用计数;然后, 每次我们调用 `clone` 时 ,计数都会增加 1 。当 `c` 超出作用域时,计数会减少 1 。我们不必像必须 调用 `Rc::clone` 来 增加引用计数那样, 调用函数来减少引用计数:`Drop` 特质的实现会在某个 `Rc<T>` 值超出作用域时,自动减少引用计数。
我们在这个例子中看不到的是,当 `b` 和 `a` 在 `main` 的末尾 超出作用域时,计数为 `0` ,并 且 `Rc<List>` 会被完全清除 。使用 `Rc<T>` 允许单个值拥 有多个所有者,而计数确保只要任何所有者仍然存在,该值就保持有效。
我们在这个例子中看不到的是,当 `b` 和 `a` 在 `main` 结束处 超出作用域时,计数为 0, 且 `Rc<List>` 会被彻底清理 。使用 `Rc<T>` 允许单个值可以 有多个所有者,而引用 计数确保只要任何一个 所有者仍然存在,该值就保持有效。
通过不可变的 引用,`Rc<T>` 允许咱们在程序的多个部分之间共用数据, 仅供读取。如果 `Rc<T>` 还 允许咱们有多个可变引用,咱们可能 会违反第四 章中讨论的一个 借用规则:对同一个地方 的多个可变借用,会导致数据竞赛 和不一致。但是, 能够修改数据是非常有用的!在下一节中,我们将讨论内部可变性模式和 `RefCell<T>` 类型,咱们可以将其 与 `Rc<T>` 结合起来使用,以应对这种 不可变性限制。
通过不可变引用,`Rc<T>` 允许咱们在程序的多个部分之间共用仅供读取的数据。若 `Rc<T>` 也 允许咱们有着 多个可变引用,咱们就 会违反第 4 章中讨论的借用规则之一 :对同一处 的多个可变借用,会导致数据竞争 和不一致。但能够修改数据是非常有用的!在下一小 节中,我们将讨论内部可变性模式和 `RefCell<T>` 类型,咱们可以与 `Rc<T>` 结合使用该类型,以处理这一 不可变性限制。
( End)