@@ -1,91 +1,126 @@
# 引用循 环会泄露内存
# 引用环会泄露内存
**Reference Cycles Can Leak Memory **
Rust 的内存安全保证,使得意外创建出从不会被清理的内存(即 * 内存泄漏, memory leak * )很难,但并非不可能。完全防止内存泄漏,并非 Rust 的保证之一,这意味着内存泄漏在 Rust 中也属于内存安全的。我们可以通过使用 `Rc<T>` 和 `RefCell<T>` ,看到 Rust 允许内存泄漏:创建出一些其中项目以循环方式相互引用的引用是可能的。这会造成内存泄漏,因为循环中的各个项目的引用计数永远不会达到 0, 进而值永远不会被弃用。
Rust 的内存安全保证,使得意外创建出从未清理过的内存(称为 * 内存泄漏, memory leak * )很难,但并非不可能。完全防止内存泄漏不是 Rust 的保证之一,这意味着内存泄漏在 Rust 中是内存安全的。通过使用 `Rc<T>` 和 `RefCell<T>` ,我们可以看到 Rust 允许内存泄漏:创建出其中项目在循环中相互指向的引用是可能的。这会造成内存泄漏,因为循环中各个项目的引用计数永远不会达到 0, 而值永远不会被弃用。
## 创建引用循环
**Creaing a Reference Cycle **
咱们以清单 15-25 中的 `List` 枚举和 `tail` 方法开始,来看看循环引用是如何发生的,以及怎样防止他:
## 创建引用环
咱们从下面清单 15-25 中的 `List` 枚举和 `tail` 方法开始,看看引用环会怎样发生,以及如何防止他。
<a name="listing_15-25"></a>
文件名:`src/main.rs`
``` rust
{ { #include .. / .. / projects / ref_cycle_demo / src / main . rs ::18 } }
use crate ::List ::{ Cons , Nil } ;
use std ::cell ::RefCell ;
use std ::rc ::Rc ;
#[ derive(Debug) ]
enum List {
Cons ( i32 , RefCell < Rc < List > > ) ,
Nil ,
}
impl List {
fn tail ( & self ) -> Option < & RefCell < Rc < List > > > {
match self {
Cons ( _ , item ) = > Some ( item ) ,
Nil = > None ,
}
}
}
```
* 清单 15-25: 包含 `RefCell<T>` 的构造列表定义,因此 我们可以修改 `Cons` 变种指向 的内容*
** 清单 15-25** :一种 包含 `RefCell<T>` 的构造列表定义,以便 我们可以修改 `Cons` 变种引用 的内容
我们正在使用清单 `15-5` 中 `List` 定义的另一种变体 。 `Cons` 变种中的第二个元素现在是 `RefCell<Rc<List>>` ,这意味着我们不像在示例 `15-24` 中那样能够 修改 `i32` 值,我们 打算修改 `Cons` 变种指向的 `List` 值。我们还添加了一 个 `tail` 方法,以便在我们有 `Cons` 变种时方便地 访问第二个项目。
我们正在使用 [ 清单 `15-5` ](./box-t.md#listing_15-5 ) 中 `List` 定义中 的另一种变种 。 `Cons` 变种中的第二个元素, 现在是 `RefCell<Rc<List>>` ,这意味着我们不打算再 像在示例 `15-24` 中那样,具备 修改 `i32` 值的能力,而 打算修改 `Cons` 变种指向的 `List` 值。我们还添加了个 `tail` 方法,以便在我们有个 `Cons` 变种时,方面我们 访问第二个项目。
在下面清单 15-26 中,咱 们添加了用到 清单 15-25 中那些 定义的 `main` 函数。此 代码创建了变量 `a` 中的一个清单 ,以及变量 `b` 中指向 `a` 中清单的一个清单。随后他将 `a` 中的清单 指向了 `b` , 这就 创建了 一个循环 引用。其间有着 一些 `println!` 语句,来 显示此 过程中不同点位的那些 引用计数。
在下面清单 15-26 中,我 们添加了个使 用清单 15-25 中的 定义的 `main` 函数。这段 代码创建了一个 变量 `a` 中的列表 ,以及一个 变量 `b` 中的 指向 `a` 中列表的列表。然后,他修改 `a` 中的列表 指向了 `b` , 从而 创建一个引用环 。其间有一些 `println!` 语句,显示这一 过程中不同点的 引用计数。
<a name="listing_15-26"></a>
文件名:`src/main.rs`
``` rust
{ { #include .. / .. / projects / ref_cycle_demo / src / main . rs :20 :} }
fn main ( ) {
let a = Rc ::new ( Cons ( 5 , RefCell ::new ( Rc ::new ( Nil ) ) ) ) ;
println! ( " a 的初始 rc 计数 = {} " , Rc ::strong_count ( & a ) ) ;
println! ( " a 的下一条目 = {:?} " , a . tail ( ) . unwrap ( ) ) ;
let b = Rc ::new ( Cons ( 10 , RefCell ::new ( Rc ::clone ( & a ) ) ) ) ;
println! ( " b 创建后 a 的 rc 计数 = {} " , Rc ::strong_count ( & a ) ) ;
println! ( " b 的初始 rc 计数 = {} " , Rc ::strong_count ( & b ) ) ;
println! ( " b 的下一条目 = {:?} " , b . tail ( ) . unwrap ( ) ) ;
if let Some ( link ) = a . tail ( ) {
* link . borrow_mut ( ) = Rc ::clone ( & b ) ;
}
println! ( " 修改 a 之后 b 的 rc 计数 = {} " , Rc ::strong_count ( & b ) ) ;
println! ( " 修改 a 之后 a 的 rc 计数 = {} " , Rc ::strong_count ( & a ) ) ;
// 取消注释下一行,就可以看到我们有个循环;
// 他将导致栈溢出。
// println! ("a 的下一条目 = {:?}", a.tail());
}
```
* 清单 15-26:创建出 相互指向的两个 `List` 的循环引用 *
** 清单 15-26** :创建包含两个 相互指向的 `List` 的引用环
我们创建一个 `Rc<List>` 实例,在变量 `a` 中持有一个 `List` 值,初始列表为 `5, Nil` 。然后我们创建一个 `Rc<List>` 实例,在变量 `b` 中保存另一个 `List` 值,其中包含值 `10` ,并指向 `a` 中的列表 。
> **译注**:其中 `a` 和 `b` 的下一条目应分别为 `a.tail().unwrap()` 和 `b.tail().unwrap()` 。
我们修改 `a` 使其指向 `b ` 而不是 Nil, 从而创建一个循环。为此, 我们使用 `tail` 方法获取对 `a` 中 `RefCell< Rc<List>> ` 的引用,我们将其放入变量 `link` 中。然后我们使用 `RefCell<Rc< List>> ` 上 的 `borrow_mut` 方法,将里面的值从一个持有 `Nil` 值的 `Rc<List>` 更改为 `b ` 中的 `Rc<List>` 。
我们以初始列表 `5, Nil` 在变量 `a ` 中,创建包含 `List` 值的 ` Rc<List>` 实例。然后,我们在变量 `b` 中创建一个包含另一 ` List` 值 的 `Rc<List>` 实例,该 `List` 值包含值 `10` 并指向 `a ` 中的列表 。
咱们暂时保持最后的 `println!` 注释掉,而运行此代码时,咱们将得到下面的输出:
我们修改 `a` 使其指向 `b` 而不是 `Nil` ,从而创建一个环。我们通过使用 `tail` 方法,获取对 `a` 中的 `RefCell<Rc<List>>` 的引用,并放置该引用于变量 `link` 中。然后,我们对 `RefCell<Rc<List>>` 使用 `borrow_mut` 方法,将内部的值从一个包含 `Nil` 值的 `Rc<List>` ,修改为 `b` 中的 `Rc<List>` 。
当我们运行这段代码时,暂时保留最后一个 `println!` 注释掉,我们将得到以下输出:
``` console
$ cargo run
Compiling ref_cycle_demo v0.1.0 (/home/lennyp /rust-lang/ref_cycle_demo )
Finished dev [unoptimized + debuginfo] target(s) in 1.20 s
Running `target/debug/ref_cycle_demo `
Compiling cons-list v0.1.0 (/home/hector /rust-lang-zh_CN/projects/cons-list )
Finished ` dev` profile [unoptimized + debuginfo] target(s) in 0.08 s
Running `target/debug/cons-list `
a 的初始 rc 计数 = 1
a 的下一条目 = Some( RefCell { value: Nil })
b 的 创建后 a 的 rc 计数 = 2
a 的下一条目 = RefCell { value: Nil }
b 创建后 a 的 rc 计数 = 2
b 的初始 rc 计数 = 1
b 的下一条目 = Some( RefCell { value: Cons(5, RefCell { value: Nil }) })
在 修改 a 之后 b 的 rc 计数 = 2
在 修改 a 之后 a 的 rc 计数 = 2
b 的下一条目 = RefCell { value: Cons(5, RefCell { value: Nil }) }
修改 a 之后 b 的 rc 计数 = 2
修改 a 之后 a 的 rc 计数 = 2
```
在咱们将 `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>` 实例仍指向他,因此该实例的内存也不能被弃用 。分配给列表内存将永远保持未被收集的 状态。为直观地表 示这个 引用循 环,咱 们创建了下图 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>` 实例仍指向他。分配给该 列表的 内存, 将永远保持未回收 状态。为了 直观地展 示这种 引用环,我 们创建了下图 15-4 中的图表。
<a name="f_15-4"></a>

* 图 15-0 4: 列表 `a` 和 `b` 相互指向的引用循环 *
** 图 15-4** :列表 `a` 和 `b` 相互指向的引用环
如果 咱们取消对 最后一个 `println! ` 的注释 并运行程序, Rust 会 尝试打印这个循环,`a` 指向 `b` 指向 `a` ,如此反复,直到程序溢出栈, overflows the stack 。
当 咱们取消注释 最后一个 `println! ` 并运行程序时 , Rust 尝试打印这个循环,其中 `a` 指向 `b` 指向 `a` ,如此循环 反复,直至栈溢出 。
与现实世界的程序相比,在 这个例子中创建一个 引用循 环的后果并不可怕:在我们创建引用循 环之后,程序就结束了。然而,如果 某个更复杂的程序在一个 循环中分配了大量的内存, 并长期占用这些内存,那么这个 程序将使用比其需要 的更多的 内存,并 可能使系统不堪重负,导致其可用内存耗尽 。
与现实世界的程序相比,这个示例中的创建 引用环的后果并不可怕:在我们创建引用环之后,程序就结束了。然而,当 某个更复杂的程序在循环中分配了大量的内存并长期占用这些内存时 ,程序将使用比所需 的更多内存,而 可能使系统不堪重负,导致其耗尽 可用内存。
创建引用循 环不容易做到 ,但也不是不可能。如果 咱们有着其中 包含 `Rc<T>` 值的 `RefCell<T>` 值,或类似的带有内部可变性与 引用计数类型 的嵌套组合,咱们 必须确保不 创建循环;咱们不能依靠 Rust 来捕获他们。创建引用循环将是咱们 程序中的逻辑错误,咱们应该 使用自动测试、代码审查和 其他软件开发实践来 减少这种错误。
创建引用环并 不容易,但也不是不可能。当 咱们有着包含 `Rc<T>` 值的 `RefCell<T>` 值,或有着 类似的带有内部可变性和 引用计数的嵌套组合时,就 必须确保没有 创建循环;咱们不能指望 Rust 来捕获他们。创建引用环属于 程序中的逻辑错误,咱们应使用自动化 测试、代码审查即 其他软件开发实践,来最大程度地 减少这种错误。
另一 避免引用循 环的办法 是重新组织咱们的 数据结构,使一些引用表达所有权,而一些引用不表达。由 此,咱们可以有由一些所有权关系和一些非所有权关系组成的循环,而只有所有权关系会影响一 个值是否可以被丢弃。在 清单 15-25 中,我们总是希望 `Cons` 变体 拥有他们的列表,所以重新组织数据结构是不可能 的。咱 们来看一个使用由父节点和子节点组成的图的示例,看看 什么时候非所有权关系是防止引用循 环的合适方式。
避免引用环的另一种解决方案 是重新组织数据结构,从而 使一些引用表达所有权,而另 一些引用不表达所有权。因 此,咱们可以有着 由一些所有权关系, 和一些非所有权关系组成的循环,而只有所有权关系会影响某 个值是否可以被弃用。在 [ 清单 15-25 ](#listing_15-25 ) 中,我们总是想要 `Cons` 变种 拥有他们的列表,所以重新组织数据结构是不可行 的。我 们来看一个使用由父节点和子节点组成的图的示例,以了解 什么时候非所有权关系, 是防止引用环的合适方式。
## 使用 `Weak<T>` 防止引用循 环
## 使用 `Weak<T>` 防止引用环
到目前为止,我们已经证明 了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,而 `Rc<T>` 实例只有在其 `strong_count` 为 `0` 时才会被清理掉 。咱们还可以通过调用 `Rc::downgrade` 并传递对 `Rc<T>` 的引用,来 创建对 `Rc<T>` 实例中值的 * 弱引用, weak reference * 。强引用是 咱们共用 `Rc<T>` 实例所有权的方式。弱引用不表达所有权关系,他们的计数不会 影响 `Rc<T>` 实例被清理的时间 。他们不会引起 引用循 环,因为任何涉及弱引用的循环,都会 在所涉及的值的强引用计数为 `0` 时被打破。
到目前为止,我们已经演示 了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,而 `Rc<T>` 实例只有在其 `strong_count` 为 0 时才会被清理。咱们还可以通过调用 `Rc::downgrade` 并传递对 `Rc<T>` 的引用,创建对 `Rc<T>` 实例中值的 * 弱引用, weak reference * 。* 强引用* 属于 咱们共用 `Rc<T>` 实例所有权的方式。* 弱引用* 则 不表达所有权关系,进而 他们的计数不影响 `Rc<T>` 实例何时 被清理。他们不会导致 引用环,因为任何涉及弱引用的循环,都将 在所涉及的值的强引用计数为 0 时被打破。
当咱们调用 `Rc::downgrade` 时,咱们会得到一个 `Weak<T>` 类型的灵巧指针。调用 `Rc::downgrade` 不是将 `Rc<T>` 实例中的 `strong_count` 增加 `1` ,而是 将 `weak_count` 增加 `1` 。与 `strong_count` 类似,`Rc<T>` 类型使用 `weak_count` 来记录 存在多少个 `Weak<T>` 引用。不同的是 ,在 `Rc<T>` 实例被清理时, `weak_count` 不需要 为 `0` 。
当咱们调用 `Rc::downgrade` 时,咱们会得到一个 `Weak<T>` 类型的灵巧指针。调用 `Rc::downgrade` 不是将 `Rc<T>` 实例中的 `strong_count` 增加 1 ,而会 将 `weak_count` 增加 1 。与 `strong_count` 类似,`Rc<T>` 类型使用 `weak_count` 来跟踪 存在多少个 `Weak<T>` 引用。区别在于 ,在 `Rc<T>` 实例的 `weak_count` 无需 为 `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` 情形 都得到处理,进而将不存在 无效指针。
作为一 个例子,我们不是 使用其项目只知道 下一项目的列表数据结构 ,而是 将创建一个其 项目了解其子项目 * 及 * 其父项目的树 。
举 个例子,我们将不再 使用其项目仅了解 下一个 项目的列表,而将创建一棵树,他的 项目了解其子项目 * 和 * 其父项目。
### 创建一个 树形数据结构:带有子节点的节点
### 创建树形数据结构
**Creating a Tree Data Structure: a Node with Child Nodes **
首先,我们将构建一棵树,其中的节点知道他们的子节点。我们将创建一个名为 `Node` 的结构体,他拥有自己的 `i32` 值以及对其子 `Node` 值的引用:
首先,我们将构建一棵树,其中的节点了解他们的子节点。我们将创建一个名为 `Node` 的结构体,保存自己的 `i32` 值以及到其子 `Node` 值的引用:
文件名:`src/main.rs`
@@ -100,10 +135,11 @@ struct Node {
}
```
我们希望 `Node` 拥有他的子节点,并且我们希望与变量共用该 所有权,以便咱 们可以直接访问树中的每个 `Node` 。为此,我们将 `Vec<T>` 项定义 为 `Rc<Node>` 类型的值。我们还打算修改那些 是另一节点的子节点的节点 ,因此我们在 `Vec<Rc<Node>>` 周围、`children` 字段中有一个 `RefCell<T>` 。
我们希望 `Node` 拥有他的子节点,并且我们希望与变量共用这一 所有权,以便我 们可以直接访问树中的每个 `Node` 。为此,我们定义 `Vec<T>` 中的项目 为 `Rc<Node>` 类型的值。我们还希望修改哪些节点 是另一节点的子节点,因此我们在 `children` 字段中有个围绕 `Vec<Rc<Node>>` 的 `RefCell<T>` 。
接下来,我们将使用我们的结构体定义,创建一个名为 `leaf` 的 `Node` 实例,其值为 `3` ,没有子节点;另一个名为 `branch` 的实例,其值为 `5` , `leaf` 是其子节点之一 ,如下清单 15-27 所示:
接下来,我们将使用我们的结构体定义,并以值 `3` 及没有子节点, 创建一个名为 `leaf` 的 `Node` 实例;并以值 `5` 和 `leaf` 作为其子节点,创建另一个名为 `branch` 的实例 ,如下清单 15-27 中 所示。
<a name="listing_15-27"></a>
文件名:`src/main.rs`
``` rust
@@ -120,28 +156,25 @@ fn main() {
}
```
* 清单 15-27: 创建一个没有子节点的 `leaf` 节点和 一个以 `leaf` 作为其子节点之一的 `branch` 节点*
** 清单 15-27** :创建一个没有子节点的 `leaf` 节点,以及 一个以 `leaf` 作为其子节点之一的 `branch` 节点
我们克隆了 `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` (译注:可调用 `Rc::strong_count(&leaf)` 查看 `leaf` 的引用计数) 。我们可以通过 `branch.children` , 从 `branch` 到达 `leaf` ,但是没有办法从 `leaf` 到达 `branch` 。原因是 `leaf` 没有到 `branch` 的引用,而 不知道他们是相关的 。我们希望 `leaf` 知道 `branch` 是他的父节点。接下来我们将实现这点 。
### 在子节点中添加到其 父节点的引用
### 在子节点中添加到父节点的引用
**Adding a Reference from a Child to Its Parent **
为了让子节点知道他的父节点,我们需要添加一个 `parent` 字段到我们的 `Node` 结构体定义。难点在于确定 `parent` 应为何种类型。我们知道他不能包含 `Rc<T>` ,因为这将以 `leaf.parent` 指向 `branch` ,而 `branch.children` 指向 `leaf` 创建一个引用环,这将导致他们的 `strong_count` 值永远不会为 0。
从另一角度考虑这些关系,父节点应拥有他的子节点:当父节点被弃用时,他的子节点也应该被弃用。然而,子节点不应拥有他的父节点:当我们弃用子节点时,父节点应该仍然存在。这正是弱引用的情形!
为了让子节点知道他的父节点,我们需要在我们的 `Node` 结构体定义中添加一个父节点字段。问题在于确定出父节点的类型是什么。我们知道他不能包含一个 `Rc<T>` ,因为这将创建一个引用循环,即 `leaf.parent` 指向 `branch` ,而 `branch.children` 指向 `leaf` ,这将导致他们的 `strong_count` 值永远为 0。
以另一种方式思考这些关系,一个父节点应该拥有他的子节点:如果一个父节点被弃用,他的子节点也应该被弃用。然而,一个子节点不应该拥有他的父节点:如果我们弃用某个子节点,父节点应该仍然存在。这就是弱引用的情况!
因此,我们将使用 `Weak<T>` 代替 `Rc<T>` ,具体来说是 `RefCell<Weak<Node>>` 。现在我们的节点结构定义如下所示:
因此,我们将使用 `Weak<T>` 而不是 `Rc<T>` 构造 `parent` 的类型,具体来说是 `RefCell<Weak<Node>>` 。现在我们的 `Node` 结构体定义看起来像下面这样:
文件名:`src/main.rs`
``` rust
use std ::cell ::RefCell ;
use std ::rc ::Rc ;
use std ::rc ::{ Rc , Weak } ;
#[ derive(Debug) ]
struct Node {
@@ -151,8 +184,9 @@ struct Node {
}
```
节点将能够引用其父节点但不拥有其父节点。在下面清单 15-28 中,我们更新了 `main` 以 使用这个新定义,这样 `leaf` 节点将有办法 引用其父节点 `branch` :
节点将能够引用其父节点, 但不会 拥有其父节点。在下面清单 15-28 中,我们更新 `main` 为 使用这个新定义,以便 `leaf` 节点将有一种 引用其父节点 `branch` 的方式。
<a name="listing_15-28"></a>
文件名:`src/main.rs`
``` rust
@@ -163,7 +197,7 @@ fn main() {
children : RefCell ::new ( vec! [ ] ) ,
} ) ;
println! ( " 叶子节点 的父节点 = {:?} " , leaf . parent . borrow ( ) . upgrade ( ) ) ;
println! ( " leaf 的父节点 = {:?} " , leaf . parent . borrow ( ) . upgrade ( ) ) ;
let branch = Rc ::new ( Node {
value : 5 ,
@@ -173,70 +207,102 @@ fn main() {
* leaf . parent . borrow_mut ( ) = Rc ::downgrade ( & branch ) ;
println! ( " 叶子节点 的父节点 = {:?} " , leaf . parent . borrow ( ) . upgrade ( ) ) ;
println! ( " leaf 的父节点 = {:?} " , leaf . parent . borrow ( ) . upgrade ( ) ) ;
}
```
* 清单 15-28: 对其父节点 `branch` 有 弱引用的 `leaf` 节点*
** 清单 15-28** :有着 对其父节点 `branch` 的 弱引用的 `leaf` 节点
创建 `leaf` 节点看起来与清单 15-27 相似,除了父字段 : `leaf` 开始时 没有父节点,所以 我们创建一个新的、空的 `Weak<Node>` 引用实例。
创建 `leaf` 节点看起来与清单 15-27 相似,除了 `parent` 段外 : `leaf` 一 开始没有父节点,因此 我们创建一个新的、空 `Weak<Node>` 引用实例。
此时,当我们试图 通过使用 `upgrade` 方法来获得 对 `leaf` 的父节点的引用时,我们得到的是一个 `None` 值。我们在第一个 `println!` 语句的输出中看到了这一 点:
此时,当我们尝 试通过使用 `upgrade` 方法获取 对 `leaf` 的父节点的引用时,我们得到 `None` 值。我们在第一个 `println!` 语句的输出中, 看到了这点:
``` console
叶子节点 的父节点 = None
leaf 的父节点 = None
```
当我们创建 `branch` 节点时,他在 `parent` 字段中也会有一 个新的 `Weak<Node>` 引用,因为 `branch` 没有父节点。我们仍然将 `leaf` 作为 `branch` 的子节点之一。一旦我们在 `branch` 中有了 `Node` 实例,我们就可以修改 `leaf` 来给 他一个到其父节点的 `Weak<Node>` 引用。我们在 `leaf` 的 `parent` 字段中的 `RefCell<Weak<Node>` 上 使用 `borrow_mut` 方法,然后我们 使用 `Rc::downgrade` 函数从 `branch` 中的 `Rc<Node>` 创建一个 对 `branch` 的 `Weak<Node>` 引用。
当我们创建 `branch` 节点时,他在 `parent` 字段中也将有 个新的 `Weak<Node>` 引用,因为 `branch` 没有父节点。我们仍然将 `leaf` 作为 `branch` 的子节点之一。一旦我们有了 `branch` 中的 `Node` 实例,我们就可以修改 `leaf` ,以给予 他一个到其父节点的 `Weak<Node>` 引用。我们对 `leaf` 的 `parent` 字段中的 `RefCell<Weak<Node>` 使用 `borrow_mut` 方法,然后使用 `Rc::downgrade` 函数, 从 `branch` 中的 `Rc<Node>` 创建对 `branch` 的 `Weak<Node>` 引用。
当我们再次打印 `leaf` 的父节点时,这次我们会 得到一个持有 `branch` 的 `Some` 变体 :现在 `leaf` 可以访问他的父节点了! 当我们打印 `leaf` 时,我们也 避免了像 清单 15-26 中那样最终以栈溢出结束的循环;`Weak<Node>` 引用被打印为 `( Weak) ` :
当我们再次打印 `leaf` 的父节点时,这次我们将 得到一个包含 `branch` 的 `Some` 变种 :现在 `leaf` 可以访问他的父节点了! 当我们打印 `leaf` 时,我们还 避免了我们在 清单 15-26 中遇到的 那样, 最终以栈溢出结束的循环;`Weak<Node>` 的 引用会 被打印为 `( Weak) ` :
``` console
叶子节点 的父节点 = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })
leaf 的父节点 = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })
```
没有无限的 输出表明这段代码没有创建引用循 环。我们也可以通过查看调用 `Rc::strong_count` 和 `Rc::weak_count` 得到的值来判断这一 点。
没有无限输出表明, 这段代码没有创建引用环。我们也可以通过查看调用 `Rc::strong_count` 和 `Rc::weak_count` 得到的值来判断这点。
### 可视化 `strong_count` 和 `weak_count` 的变化
### 可视化 `strong_count` 到 `weak_count` 的变化
**Visualizing Changes to `strong_count` and `weak_count`**
咱们来看看通过创建一个新的内部作用域并将 `branch` 的创建移到该作用域中,`Rc<Node>` 实例的 `strong_count` 和 `weak_count` 值如何变化。通过这样做,我们可以看到在 `branch` 被创建后,当他离开作用域时,会发生什么。修改部分如下清单 15-29 所示:
咱们来通过创建一个新的内层作用域,并迁移 `branch` 的创建到该作用域中,看看 `Rc<Node>` 实例的 `strong_count` 和 `weak_count` 值会如何变化。通过这样做,我们可以看到在 `branch` 被创建时,以及当他超出作用域而被弃用时,分别会发生什么。相关修改如下清单 15-29 中所示。
<a name="listing_15-29"></a>
文件名:`src/main.rs`
``` rust
{ { #include .. / .. / projects / tree_demo / src / main . rs :11 :} }
fn main ( ) {
let leaf = Rc ::new ( Node {
value : 3 ,
parent : RefCell ::new ( Weak ::new ( ) ) ,
children : RefCell ::new ( vec! [ ] ) ,
} ) ;
println! ( " leaf 的强引用计数 = {} ,弱引用计数 = {} " ,
Rc ::strong_count ( & leaf ) ,
Rc ::weak_count ( & leaf )
) ;
{
let branch = Rc ::new ( Node {
value : 5 ,
parent : RefCell ::new ( Weak ::new ( ) ) ,
children : RefCell ::new ( vec! [ Rc ::clone ( & leaf ) ] ) ,
} ) ;
* leaf . parent . borrow_mut ( ) = Rc ::downgrade ( & branch ) ;
println! ( " branch 的强引用计数 = {} ,弱引用计数 = {} " ,
Rc ::strong_count ( & branch ) ,
Rc ::weak_count ( & branch )
) ;
println! ( " leaf 的强引用计数 = {} ,弱引用计数 = {} " ,
Rc ::strong_count ( & leaf ) ,
Rc ::weak_count ( & leaf )
) ;
}
println! ( " leaf 的父节点 = {:?} " , leaf . parent . borrow ( ) . upgrade ( ) ) ;
println! ( " leaf 的强引用计数 = {} ,弱引用计数 = {} " ,
Rc ::strong_count ( & leaf ) ,
Rc ::weak_count ( & leaf )
) ;
}
```
* 清单 15-29: 在内层作用域中创建 `branch` 并检查强引用和弱引用计数*
** 清单 15-29** :在内层作用域中创建 `branch`, 并检查强引用计数 和弱引用计数
`leaf` 创建后,其 `Rc<Node>` 的 `strong count` 为 `1` , `weak count` 为 `0` 。
`leaf` 创建后,他的 `Rc<Node>` 有着 1 的 `strong count` , 0 的 `weak count` 。在内层作用域中,我们创建 `branch` 并将其与 `leaf` 关联,在我们打印计数处,`branch` 中的 `Rc<Node>` 将有着 1 的强引用计数和 1 的弱引用计数(因为 `leaf.parent` 以一个 `Weak<Node>` 指向 `branch` )。当我们打印 `leaf` 中的计数时,我们将看到他有着 2 的强引用计数,因为 `branch` 现在有着存储在 `branch.children` 字段中 `leaf` 的 `Rc<Node>` 的克隆,但 `leaf` 仍将有着 0 的弱引用计数 。
在内层作用域中,我们创建了 `branch` 并将其与 `leaf` 关联,此时当我们打印计数时, `branch` 中的 `Rc<Node>` 将有 `1` 的强计数和 `1` 的弱计数(因为 `leaf.parent` 指向 `branch` 的是 `Weak< Node> ` )。当我们打印 `leaf` 中的计数时,我们将看到他的强计数为 `2` ,因为 `branch` 现在有一个保存在 `branch.child ren` 中的 `leaf` 的 `Rc< Node> ` 的克隆,但仍然会有一个弱计数为 `0` 。
在内层作用域结束后, `branch` 超出作用域,进而 `Rc<Node>` 的强引用计数降为 0, 所以他的 ` Node` 被弃用。来自 `leaf.pa rent ` 的弱引用计数 1 并不影响 ` Node` 是否被弃用,因此我们没有任何内存泄露!
当内层作用域结束时,`branch` 超出作用域,`Rc<Node>` 的强计数减少到 `0` ,所以他 的 `Node` 被弃用。来自 `leaf.parent` 的 弱计数 `1 ` 对 ` Node` 是否被弃用没有影响,所以我们没有任何内存泄露!
当我们尝试在这个作用域结束后访问 `leaf` 的父节点时,我们将再次得到 `None` 。在程序的结束处,`leaf` 中 的 `Rc< Node> ` 有着 1 的强引用计数, 弱计数为 `0 ` ,因为现在变量 `leaf` 再次成为对这个 `Rc< Node> ` 的唯一引用。
如果我们在作用域结束后试图访问 `leaf` 的父节点,我们会再次得到 `None` 。在程序结束时,`leaf` 中的 `Rc< Node> ` 的强计数为 `1` ,弱计数为 `0` ,因为现在变量 `l eaf` 又是对 `Rc<Node>` 的唯一引用 。
所有管理计数和值弃用的逻辑,都内置于 `Rc<T>` 和 `Weak<T>` 及他们的 `Drop` 特质实现中。通过在 `Node` 的定义中指定子节点与其父节点的关系应是 `Weak<T>` 引用,咱们可以让父节点指向子节点,反之亦然,而不会产生引用循环和内存泄漏。
所有管理引用计数与值弃用的逻辑,都内置于 `Rc<T>` 和 `Weak<T>` 及其 `Drop` 特质的实现中。通过在 ` Node` 的定义中指定子节点到其父节点的关系应为 `W eak<T>` 引用,咱们可以在不会创建引用环和内存泄漏下,让父节点指向子节点,反之亦然 。
# 本章小结
本 章介绍了如何使用灵巧指针来进行 与 Rust 默认的普通引用 不同的 保证和取舍 。`Box<T>` 类型有一个 已知的 大小,指向在内存 堆上分配的数据。`Rc<T>` 类型记录了对内存 堆上数据的引用数量,因此 数据可以有多个所有者。`RefCell<T>` 类型及 其内部可变性为我们提供了一种类型,当我们 需要不可变类型但 需要改变该类型的内部值时,我们可以使用这种 类型;他还在运行时而不是在编译时强制执行借用规则。
这一 章介绍了如何使用灵巧指针,来做出 与 Rust 在 默认情况下以普通引用所做出 的不同保证和权衡 。`Box<T>` 类型有着 已知大小,并 指向堆上分配的数据。`Rc<T>` 类型记录堆上数据的引用的 数量,以便 数据可以有多个所有者。`RefCell<T>` 类型通过 其内部可变性,给予我们一种可以在 需要不可变类型,但又 需要改变该类型的内部值时使用的 类型;他还会 在运行时而不是在编译时强制执行借用规则检查 。
我们 还讨论了 `Deref` 和 `Drop` 特质,这 两个特质实现了灵巧指针的很多 功能。我们探讨了可能导致内存泄露的引用循环以及如何 使用 `Weak<T>` 来防止它 们。
此外,这一章 还讨论了 `Deref` 和 `Drop` 两个特质,他们 实现了灵巧指针的大部分 功能。我们探讨了可能导致内存泄露的引用环,以及怎样 使用 `Weak<T>` 来防止他 们。
如果本 章引起了你 的兴趣,并且你想 实现你 自己的灵巧指针,请查看 ["The Rustonomicon" ](https://doc.rust-lang.org/nomicon/index.html ) ,以获得 更多有用的信息。
若这一 章引起了咱们 的兴趣,并且咱们打算 实现自己的灵巧指针,请查看 ["The Rustonomicon" ](https://doc.rust-lang.org/nomicon/index.html ) ,以获取 更多有用的信息。
接下来,我们将讨论 Rust 中的并发问题。咱们甚至会 了解到一些新的灵巧指针。
接下来,我们将讨论 Rust 中的并发问题。咱们甚至还将 了解到一些新的灵巧指针。
( End)