@ -1,14 +1,13 @@
## 引用循环与内存泄漏
> [ch15-06-reference-cycles.md ](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md )
> < br >
> commit c06006157b14b3d47b5c716fc392b77f3b2e21ce
<!-- https://github.com/rust - lang/book/blob/main/src/ch15 - 06 - reference - cycles.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 ** 内存泄漏**( _memory leak_) ) , 但并不是 不可能。Rust 并不保证完全防止内存泄漏,这意味着内存泄漏在 Rust 中被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0, 持有的数据也就永远不会被释放。
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 ** 内存泄漏**, _memory leak_) , 但并非 不可能。Rust 并不保证完全防止内存泄漏,这意味着内存泄漏在 Rust 中被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出 Rust 允许出现内存泄漏 :创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0, 持有的数据也就永远不会被释放。
### 制造引用循环
让我们看看引用循环是如何发生的以及如何避免它。 以示例 15-25 中的 `List` 枚举和 `tail` 方法的定义开始:
让我们看看引用循环是如何发生的以及如何避免它, 以示例 15-25 中的 `List` 枚举和 `tail` 方法的定义开始:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -16,9 +15,9 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-25/src/main.rs}}
```
< span class = "caption" > 示例 15-25: 一个存放 `RefCell` 的 cons list 定义,这样可以修改 `Cons` 成员 所引用的数据</ span >
< span class = "caption" > 示例 15-25: 一个存放 `RefCell` 的 cons list 定义,这样可以修改 `Cons` 变体 所引用的数据</ span >
这里采用了示例 15-5 中 `List` 定义的另一种变体。现在 `Cons` 成员 的第二个元素是 `RefCell<Rc<List>>` ,这意味着不同于像示例 15-24 那样能够修改 `i32` 的值,我们希望能够修改 `Cons` 成员 所指向的 `List` 。这里还增加了一个 `tail` 方法来方便我们在有 `Cons` 成员 的时候访问其第二项。
这里采用了示例 15-5 中 `List` 定义的另一种变体。现在 `Cons` 变体 的第二个元素是 `RefCell<Rc<List>>` ,这意味着不同于像示例 15-24 那样能够修改 `i32` 的值,我们希望能够修改 `Cons` 变体 所指向的 `List` 。这里还增加了一个 `tail` 方法来方便我们在有 `Cons` 变体 的时候访问其第二项。
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `a` 中的列表指向 `b` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
@ -30,9 +29,9 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
< span class = "caption" > 示例 15-26: 创建一个引用循环: 两个 `List` 值互相指向彼此</ span >
这里在变量 `a` 中创建了一个 `Rc<List>` 实例来存放初值为 `5, Nil` 的 `List` 值。接着在变量 `b` 中创建了存放包含值 10 和指向列表 `a` 的 `List` 的另一个 `Rc<List>` 实例。
这里在变量 `a` 中创建了一个 `Rc<List>` 实例来存放初值为 `5, Nil` 的 `List` 值。接着在变量 `b` 中创建了存放包含值 ` 10` 和指向列表 `a` 的 `List` 的另一个 `Rc<List>` 实例。
最后, 修改 `a` 使其指向 `b` 而不是 `Nil` ,这就创建了一个循环。为此需要使用 `tail` 方法获取 `a` 中 `RefCell<Rc<List>>` 的引用,并放入变量 `link` 中。接着使用 `RefCell<Rc<List>>` 的 `borrow_mut` 方法将其值从存放 `Nil` 的 `Rc<List>` 修改为 `b` 中的 `Rc<List>` 。
下来 修改 `a` 使其指向 `b` 而不是 `Nil` ,这就创建了一个循环。为此需要使用 `tail` 方法获取 `a` 中 `RefCell<Rc<List>>` 的引用,并放入变量 `link` 中。接着使用 `RefCell<Rc<List>>` 的 `borrow_mut` 方法将其值从存放 `Nil` 的 `Rc<List>` 修改为 `b` 中的 `Rc<List>` 。
如果保持最后的 `println!` 行注释并运行代码,会得到如下输出:
@ -40,7 +39,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
{{#include ../listings/ch15-smart-pointers/listing-15-26/output.txt}}
```
可以看到将列表 `a` 修改为指向 `b` 之后, `a` 和 `b` 中的 `Rc<List>` 实例的引用计数都是 2。在 `main` 的结尾, Rust 丢弃 `b` ,这会使 `b` `Rc<List>` 实例的引用计数从 2 减为 1。然而,`b` `Rc<List>` 不能 被回收,因为其引用计数是 1 而不是 0。接下来 Rust 会丢弃 `a` 将 `a` `Rc<List>` 实例的引用计数从 2 减为 1。这个实例也不能被回收, 因为 `b` `Rc<List>` 实例依然引用它,所以其引用计数是 1。这些列表的内存将永远保持未被回收的状态。为了更形象的展示,我们创建了一个如图 15-4 所示的引用循环 :
可以看到将列表 `a` 修改为指向 `b` 之后, `a` 和 `b` 中的 `Rc<List>` 实例的引用计数都是 2。在 `main` 的结尾, Rust 丢弃 `b` ,这会使 `b` `Rc<List>` 实例的引用计数从 2 减为 1。此时该 `Rc<List>` 实例并不会 被回收,因为其引用计数是 1 而不是 0。接下来 Rust 会丢弃 `a` 将 `a` `Rc<List>` 实例的引用计数从 2 减为 1。这个实例也不能被回收, 由于另一个 `Rc<List>` 实例依然引用它,所以其引用计数是 1。这些列表的内存将永远保持未被回收的状态。为了更直观地展示这一引用循环,我们创建了一个如图 15-4 所示的示意图 :
< img alt = "Reference cycle of lists" src = "img/trpl15-04.svg" class = "center" / >
@ -48,27 +47,25 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
如果取消最后 `println!` 的注释并运行程序, Rust 会尝试打印出 `a` 指向 `b` 指向 `a` 这样的循环直到栈溢出。
相比真实世界的程序,这个例子中创建引用循环的结果并不可怕。 创建了引用循环之后程序立刻就结束了。如果在更为复杂的程序中并在循环里分配了很多内存并占有很长时间,这个程序会使用多于它所需要的内存,并有可能压垮系统并造成没有内存可供使用。
相比真实世界的程序,这个例子中创建引用循环的结果并不可怕: 创建了引用循环之后程序立刻就结束了。如果在更为复杂的程序中并在循环里分配了很多内存并占有很长时间,这个程序会使用多于它所需要的内存,并有可能压垮系统并造成没有内存可供使用。
创建引用循环并不容易,但也不是不可能。如果你有包含 `Rc<T>` 的 `RefCell<T>` 值或类似的嵌套结合了内部可变性和引用计数的类型,请务必小心确保你没有形成一个引用循环;你无法指望 Rust 帮你捕获它们。创建引用循环是一个程序上的逻辑 bug, 你应该使用自动化测试、代码评审和其他软件开发最佳实践来使其最小化。
另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有。换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃。在示例 15-25 中,我们总是希望 `Cons` 成员 拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父节点和子节点构成的图的例子,观察何时是使用无所有权的关系来避免引用循环的合适时机。
另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有。换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃。在示例 15-25 中,我们总是希望 `Cons` 变体 拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父节点和子节点构成的图的例子,观察何时是使用无所有权的关系来避免引用循环的合适时机。
### 避免引用循环:将 `Rc<T>` 变为 `Weak<T>`
### 使用 `Weak<T>` 防止引用循环
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,和只在其 `strong_count` 为 0 时才会被清理的 `Rc<T>` 实例。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的 ** 弱引用**( _weak reference_) 。强引用代表如何共享 `Rc<T>` 实例的所有权。弱引用并不属于 所有权关系,当 `Rc<T>` 实例被清理时其计数没有影响。它们不会造成引用循环,因为任何涉及弱引用的循环会在其相关的值的强引用计数为 0 时被打断。
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,和只在其 `strong_count` 为 0 时 `Rc<T>` 实例才会被清理 。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的**弱引用**( _weak reference_) 。强引用代表如何共享 `Rc<T>` 实例的所有权;弱引用不表达 所有权关系,当 `Rc<T>` 实例被清理时其计数没有影响。它们不会造成引用循环,因为任何涉及弱引用的循环会在其相关的值的强引用计数为 0 时被打断。
调用 `Rc::downgrade` 时会得到 `Weak<T>` 类型的智能指针。不同于将 `Rc<T>` 实例的 `strong_count` 加 1, 调用 `Rc::downgrade` 会将 `weak_count` 加 1。`Rc< T > ` 类型使用 `weak_count` 来记录其存在多少个 `Weak<T>` 引用,类似于 `strong_count` 。其区别在于 `weak_count` 无需计数为 0 就能使 `Rc<T>` 实例被清理。
强引用代表如何共享 `Rc<T>` 实例的所有权,但弱引用并不属于所有权关系。它们不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断 。
因为 `Weak<T>` 引用的值可能已经被丢弃了,为了使用 `Weak<T>` 所指向的值,我们必须确保其值仍然有效。为此可以调用 `Weak<T>` 实例的 `upgrade` 方法,这会返回 `Option<Rc<T>>` 。如果 `Rc<T>` 值还未被丢弃,则结果是 `Some` ;如果 `Rc<T>` 已被丢弃,则结果是 `None` 。因为 `upgrade` 返回一个 `Option<Rc<T>>` , Rust 会确保处理 `Some` 和 `None` 的情况,所以它不会返回无效指针 。
因为 `Weak<T>` 引用的值可能已经被丢弃了,为了使用 `Weak<T>` 所指向的值,我们必须确保其值仍然有效。为此可以调用 `Weak<T>` 实例的 `upgrade` 方法,这会返回 `Option<Rc<T>>` 。如果 `Rc<T>` 值还未被丢弃,则结果是 `Some` ;如果 `Rc<T>` 已被丢弃,则结果是 `None` 。因为 `upgrade` 返回一个 `Option<Rc<T>>` , Rust 会确保处理 `Some` 和 `None` 的情况,所以它不会返回非法指针。
我们会创建一个某项知道其子项和父项的树形结构的例子,而不是只知道其下一项的列表。
作为示例,我们不再使用只知道下一个元素的列表,而是创建一个既知道子节点又知道父节点的树结构。
#### 创建树形数据结构:带有子节点的 `Node`
在最开始,我们将会构建一个带有子节点的树。让我们 创建一个用于存放其拥有所有权的 `i32` 值和其子节点引用的 `Node` :
首先,我们将构建一个节点能够知道其子节点的树。 创建一个用于存放其拥有所有权的 `i32` 值和对 其子 `Node` 的引用 :
< span class = "filename" > 文件名: src/main.rs< / span >
@ -78,7 +75,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
我们希望 `Node` 能够拥有其子节点,同时也希望能将所有权共享给变量,以便可以直接访问树中的每一个 `Node` ,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>` 。我们还希望能修改其他节点的子节点,所以 `children` 中 `Vec<Rc<Node>>` 被放进了 `RefCell<T>` 。
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch` ,如示例 15-27 所示:
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 ` 3` 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch` ,如示例 15-27 所示:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -88,7 +85,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
< span class = "caption" > 示例 15-27: 创建没有子节点的 `leaf` 节点和以 `leaf` 作为子节点的 `branch` 节点</ span >
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf`和`branch`。可以通过 `branch.children` 从 `branch` 中获得 `leaf` ,不过无法从 `leaf` 到 `branch` 。`leaf` 没有到 `branch` 的引用且并不知道它们相互关联。我们希望 `leaf` 知道 `branch` 是其父节点。稍后 我们会这么做。
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf` 和 `branch` 。可以通过 `branch.children` 从 `branch` 中获得 `leaf` ,不过无法从 `leaf` 得 到 `branch` 。`leaf` 没有到 `branch` 的引用且并不知道它们相互关联。我们希望 `leaf` 知道 `branch` 是其父节点。接下来 我们会这么做。
#### 增加从子到父的引用
@ -112,9 +109,9 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:there}}
```
< span class = "caption" > 示例 15-28: 一个 `leaf` 节点,其拥有指向其父节点 `branch` 的 `Weak` 引用</ span >
< span class = "caption" > 示例 15-28: 一个 `leaf` 节点,其拥有指向其父节点 `branch` 的弱 引用</ span >
创建 `leaf` 节点类似于示例 15-27, 除了 `parent` 字段有所不同:`leaf` 开始时没有父节点,所以我们新建了一个空的 `Weak ` 引用实例。
创建 `leaf` 节点类似于示例 15-27, 除了 `parent` 字段有所不同:`leaf` 开始时没有父节点,所以我们新建了一个空的 `Weak <Node> ` 引用实例。
此时,当尝试使用 `upgrade` 方法获取 `leaf` 的父节点引用时,会得到一个 `None` 值。如第一个 `println!` 输出所示:
@ -156,9 +153,9 @@ children: RefCell { value: [] } }] } })
## 总结
这一章涵盖了如何使用智能指针来做出不同于 Rust 常规引用默认所提供的保证与取舍。`Box< T > ` 有一个已知的大小并指向分配在堆上的数据。`Rc< T > ` 记录了堆上数据的引用数量以便可以拥有多个所有者。`RefCell< T > ` 和其内部可变性提供了一个可以用于当需要不可变类型但是需要改变其内部值能力的类型,并在运行时而不是编译时检查借用规则 。
这一章涵盖了如何使用智能指针来做出不同于 Rust 常规引用默认所提供的保证与取舍。`Box< T > ` 有一个已知的大小并指向分配在堆上的数据。`Rc< T > ` 记录了堆上数据的引用计数从而允许多个所有者。`RefCell< T > ` 类型及其内部可变性允许我们在保持类型不可变的前提下更改其内部值;它也在运行时而非编译时执行借用规则检查 。
我们还介绍了提供了很多智能指针功能的 trait `Deref` 和 `Drop` 。同时探索了会造成内存泄漏的引用循环,以及如何使用 `Weak<T>` 来避免它们。
我们还讨论了 trait `Deref` 和 `Drop` ,它们实现了智能指针的许多功能 。同时探索了会造成内存泄漏的引用循环,以及如何使用 `Weak<T>` 来避免它们。
如果本章内容引起了你的兴趣并希望现在就实现你自己的智能指针的话,请阅读 [“The Rustonomicon”][nomicon] 来获取更多有用的信息。