@ -1,7 +1,7 @@
## 引用循环与内存泄漏
> [ch15-06-reference-cycles.md ](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md ) < br >
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
> commit bd27a8b72336610c9a200f0ca932ffc8b6fb5ee1
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 ** 内存泄漏**( _memory leak_) ) , 但并不是不可能。与在编译时拒绝数据竞争不同, Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0, 其值也永远不会被丢弃。
@ -12,25 +12,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
# fn main() {}
use std::rc::Rc;
use std::cell::RefCell;
use crate::List::{Cons, Nil};
#[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,
}
}
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-25/src/main.rs}}
```
< span class = "caption" > 示例 15-25: 一个存放 `RefCell` 的 cons list 定义,这样可以修改 `Cons` 成员所引用的数据</ span >
@ -42,47 +24,7 @@ impl List {
< span class = "filename" > 文件: src/main.rs< / span >
```rust
# use crate::List::{Cons, Nil};
# use std::rc::Rc;
# use std::cell::RefCell;
# #[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,
# }
# }
# }
#
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-26/src/main.rs:here}}
```
< span class = "caption" > 示例 15-26: 创建一个引用循环: 两个 `List` 值互相指向彼此</ span >
@ -93,17 +35,11 @@ fn main() {
如果保持最后的 `println!` 行注释并运行代码,会得到如下输出:
```text
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2
```console
{{#include ../listings/ch15-smart-pointers/listing-15-26/output.txt}}
```
可以看到将列表 `a` 修改为指向 `b` 之后, `a` 和 `b` 中的 `Rc<List>` 实例的引用计数都是 2。在 `main` 的结尾, Rust 首先丢弃变量 `b` ,这会使 `b` 中 `Rc<List>` 实例的引用计数减 1。然而, 因为 `a` 仍然引用 `b` 中的 `Rc<List>` , `Rc< List > ` 的引用计数是 1 而不是 0, 所以 `b` 中的 `Rc<List>` 在堆上的内存不会被丢弃。接下来 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。然而, `b` `Rc<List>` 不能被回收,因为其引用计数是 1 而不是 0。接下来 Rust 会丢弃 `a` 将 `a` `Rc<List>` 实例的引用计数从 2 减为 1。这个实例也不能被回收, 因为 `b` `Rc<List>` 实例依然引用它,所以其引用计数是 1。这些列表的内存将永远保持未被回收的状态。为了更形象的展示, 我们创建了一个如图 15-4 所示的引用循环:
< img alt = "Reference cycle of lists" src = "img/trpl15-04.svg" class = "center" / >
@ -119,11 +55,11 @@ a rc count after changing a = 2
### 避免引用循环:将 `Rc<T>` 变为 `Weak<T>`
到目前为止,我们已经展示了调用 `Rc::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,和只在其 `strong_count` 为 0 时才会被清理的 `Rc<T>` 实例。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的 ** 弱引用**( _weak reference_) 。调用 `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::clone` 会增加 `Rc<T>` 实例的 `strong_count` ,和只在其 `strong_count` 为 0 时才会被清理的 `Rc<T>` 实例。你也可以通过调用 `Rc::downgrade` 并传递 `Rc<T>` 实例的引用来创建其值的 ** 弱引用**( _weak reference_) 。调用 `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< 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` 的情况,所以它不会返回非法指针。
我们会创建一个某项知道其子项**和**父项的树形结构的例子,而不是只知道其下一项的列表。
@ -134,14 +70,7 @@ a rc count after changing a = 2
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
children: RefCell< Vec < Rc < Node > >>,
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:here}}
```
我们希望能够 `Node` 拥有其子节点,同时也希望通过变量来共享所有权,以便可以直接访问树中的每一个 `Node` ,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>` 。我们还希望能修改其他节点的子节点,所以 `children` 中 `Vec<Rc<Node>>` 被放进了 `RefCell<T>` 。
@ -151,26 +80,7 @@ struct Node {
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
# use std::rc::Rc;
# use std::cell::RefCell;
#
# #[derive(Debug)]
# struct Node {
# value: i32,
# children: RefCell< Vec < Rc < Node > >>,
# }
#
fn main() {
let leaf = Rc::new(Node {
value: 3,
children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
value: 5,
children: RefCell::new(vec![Rc::clone(& leaf)]),
});
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-27/src/main.rs:there}}
```
< span class = "caption" > 示例 15-27: 创建没有子节点的 `leaf` 节点和以 `leaf` 作为子节点的 `branch` 节点</ span >
@ -188,15 +98,7 @@ fn main() {
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell< Weak < Node > >,
children: RefCell< Vec < Rc < Node > >>,
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:here}}
```
这样,一个节点就能够引用其父节点,但不拥有其父节点。在示例 15-28 中,我们更新 `main` 来使用新定义以便 `leaf` 节点可以通过 `branch` 引用其父节点:
@ -204,35 +106,7 @@ struct Node {
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
# use std::rc::{Rc, Weak};
# use std::cell::RefCell;
#
# #[derive(Debug)]
# struct Node {
# value: i32,
# parent: RefCell< Weak < Node > >,
# children: RefCell< Vec < Rc < Node > >>,
# }
#
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
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!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:there}}
```
< span class = "caption" > 示例 15-28: 一个 `leaf` 节点,其拥有指向其父节点 `branch` 的 `Weak` 引用</ span >
@ -264,58 +138,7 @@ children: RefCell { value: [] } }] } })
< span class = "filename" > 文件名: src/main.rs< / span >
```rust
# use std::rc::{Rc, Weak};
# use std::cell::RefCell;
#
# #[derive(Debug)]
# struct Node {
# value: i32,
# parent: RefCell< Weak < Node > >,
# children: RefCell< Vec < Rc < Node > >>,
# }
#
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
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 strong = {}, weak = {}",
Rc::strong_count(& branch),
Rc::weak_count(& branch),
);
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(& leaf),
Rc::weak_count(& leaf),
);
}
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(& leaf),
Rc::weak_count(& leaf),
);
}
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-29/src/main.rs:here}}
```
< span class = "caption" > 示例 15-29: 在内部作用域创建 `branch` 并检查其强弱引用计数</ span >
@ -336,6 +159,6 @@ fn main() {
如果本章内容引起了你的兴趣并希望现在就实现你自己的智能指针的话,请阅读 [“The Rustonomicon”][nomicon] 来获取更多有用的信息。
[nomicon]: https://doc.rust-lang.org/stable/nomicon/
接下来,让我们谈谈 Rust 的并发。届时甚至还会学习到一些新的对并发有帮助的智能指针。
[nomicon]: https://doc.rust-lang.org/nomicon/index.html