trpl-zh-cn/src/ch15-04-rc.md

131 lines
6.4 KiB
Markdown
Raw Normal View History

2017-03-16 00:02:13 +08:00
## `Rc<T>` 引用计数智能指针
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-04-rc.md)
> <br>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
大部分情况下所有权是非常明确的可以准确的知道哪个变量拥有某个值。然而并不总是如此有时确实可能需要多个所有者。为此Rust 有一个叫做`Rc<T>`的类型。它的名字是**引用计数***reference counting*)的缩写。引用计数意味着它记录一个值引用的数量来知晓这个值是否仍在被使用。如果这个值有零个引用,就知道可以在没有有效引用的前提下清理这个值。
根据现实生活场景来想象的话,它就像一个客厅的电视。当一个人进来看电视时,他打开电视。其他人也会进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候关掉了电视,正在看电视人肯定会抓狂的!
`Rc<T>`用于当我们希望在堆上分配一些内存供程序的多个部分读取,而且无法在编译时确定程序的那一部分会最后结束使用它。如果我们知道的话那么常规的所有权规则会在编译时强制起作用。
注意`Rc<T>`只能用于单线程场景;下一章并发会涉及到如何在多线程程序中进行引用计数。如果尝试在多线程中使用`Rc<T>`则会得到一个编译错误。
### 使用`Rc<T>`分享数据
2017-09-27 16:08:38 +08:00
让我们回到示例 15-5 中的 cons list 例子。在示例 15-11 中尝试使用`Box<T>`定义的`List`。首先创建了一个包含 5 接着是 10 的列表实例。之后我们想要创建另外两个列表:一个以 3 开始并后接第一个包含 5 和 10 的列表,另一个以 4 开始其后**也**是第一个列表。换句话说,我们希望这两个列表共享第三个列表的所有权,概念上类似于图 15-10
2017-03-16 00:02:13 +08:00
<img alt="Two lists that share ownership of a third list" src="img/trpl15-03.svg" class="center" />
<span class="caption">图 15-10: 两个列表, `b``c`, 共享第三个列表 `a` 的所有权</span>
2017-03-16 00:02:13 +08:00
2017-09-27 16:08:38 +08:00
尝试使用`Box<T>`定义的`List`并不能工作,如示例 15-11 所示:
2017-03-16 00:02:13 +08:00
<span class="filename">文件: src/main.rs</span>
2017-03-16 00:02:13 +08:00
```rust,ignore
enum List {
Cons(i32, Box<List>),
Nil,
}
use 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));
}
```
<span class="caption">示例 15-11: 具有两个使用 `Box<T>` 的列表,试图共享第三个列表的所有权,是不能正常运行的/span>
2017-03-16 00:02:13 +08:00
编译会得出如下错误:
```
error[E0382]: use of moved value: `a`
--> src/main.rs:13:30
|
12 | let b = Cons(3, Box::new(a));
| - value moved here
13 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
= note: move occurs because `a` has type `List`, which does not
implement the `Copy` trait
```
`Cons`成员拥有其储存的数据,所以当创建`b`列表时将`a`的所有权移动到了`b`。接着当再次尝使用`a`创建`c`时,这不被允许因为`a`的所有权已经被移动。
相反可以改变`Cons`的定义来存放一个引用,不过接着必须指定生命周期参数,而且在构造列表时,也必须使列表中的每一个元素都至少与列表本身存在的一样久。否则借用检查器甚至都不会允许我们编译代码。
2017-03-16 00:02:13 +08:00
2017-09-27 16:08:38 +08:00
如示例 15-12 所示,可以将`List`的定义从`Box<T>`改为`Rc<T>`
2017-03-16 00:02:13 +08:00
<span class="filename">文件名: src/main.rs</span>
2017-03-16 00:02:13 +08:00
```rust
enum List {
Cons(i32, Rc<List>),
Nil,
}
use 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, a.clone());
let c = Cons(4, a.clone());
}
```
<span class="caption">示例 15-12: 使用 `Rc<T>` 定义的 `List`</span>
2017-03-16 00:02:13 +08:00
注意必须为`Rc`增加`use`语句因为它不在 prelude 中。在`main`中创建了存放 5 和 10 的列表并将其存放在一个叫做`a`的新的`Rc`中。接着当创建`b`和`c`时,我们对`a`调用了`clone`方法。
### 克隆`Rc<T>`会增加引用计数
2017-09-27 16:08:38 +08:00
之前我们见过`clone`方法,当时使用它来创建某些数据的完整拷贝。但是对于`Rc<T>`来说,它并不创建一个完整的拷贝。`Rc<T>`存放了**引用计数**,也就是说,一个存在多少个克隆的计数器。让我们像示例 15-13 那样在创建`c`时增加一个内部作用域,并在不同的位置打印出关联函数`Rc::strong_count`的结果。`Rc::strong_count`返回传递给它的`Rc`值的引用计数,而在本章的稍后部分介绍避免引用循环时讲到它为什么叫做`strong_count`。
2017-03-16 00:02:13 +08:00
<span class="filename">文件名: src/main.rs</span>
2017-03-16 00:02:13 +08:00
```rust
# enum List {
# Cons(i32, Rc<List>),
# Nil,
# }
#
# use List::{Cons, Nil};
# use std::rc::Rc;
#
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("rc = {}", Rc::strong_count(&a));
let b = Cons(3, a.clone());
println!("rc after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, a.clone());
println!("rc after creating c = {}", Rc::strong_count(&a));
}
println!("rc after c goes out of scope = {}", Rc::strong_count(&a));
}
```
<span class="caption">示例 15-13: 打印输出引用计数</span>
2017-03-16 00:02:13 +08:00
这会打印出:
```
rc = 1
rc after creating b = 2
rc after creating c = 3
rc after c goes out of scope = 2
```
不难看出`a`的初始引用计数是一。接着每次调用`clone`,计数会加一。当`c`离开作用域时,计数减一,这发生在`Rc<T>`的`Drop` trait 实现中。这个例子中不能看到的是当`b`接着是`a`在`main`函数的结尾离开作用域时,包含 5 和 10 的列表的引用计数会是 0这时列表将被丢弃。这个策略允许拥有多个所有者而引用计数会确保任何所有者存在时这个值保持有效。
在本部分的开始,我们说`Rc<T>`只允许程序的多个部分读取`Rc<T>`中`T`的不可变引用。如果`Rc<T>`允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和`RefCell<T>`类型,它可以与`Rc<T>`结合使用来处理不可变性的限制。