mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2025-02-23 04:32:16 +08:00
update to ch15-06
This commit is contained in:
parent
be4f7e7281
commit
946f5d5fc0
@ -1,17 +1,17 @@
|
||||
## 使用`Box <T>`指向堆上的数据
|
||||
|
||||
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/main/src/ch15-01-box.md) <br>
|
||||
> commit a203290c640a378453261948b3fee4c4c6eb3d0f
|
||||
> commit 359895c6b2e440275a663ee1a3c17e6a94fdc62b
|
||||
|
||||
最简单直接的智能指针是 _box_,其类型是 `Box<T>`。 box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
|
||||
|
||||
除了数据被储存在堆上而不是栈上之外,box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
|
||||
|
||||
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
|
||||
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
|
||||
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
|
||||
* 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
|
||||
* 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
|
||||
* 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
|
||||
|
||||
我们会在 “box 允许创建递归类型” 部分展示第一种场景。在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象**(_trait object_),第十七章刚好有一整个部分 “为使用不同类型的值而设计的 trait 对象” 专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
|
||||
我们会在 [“box 允许创建递归类型”](#enabling-recursive-types-with-boxes) 部分展示第一种场景。在第二种情况中,转移大量数据的所有权可能会花费很长的时间,因为数据在栈上进行了拷贝。为了改善这种情况下的性能,可以通过 box 将这些数据储存在堆上。接着,只有少量的指针数据在栈上被拷贝。第三种情况被称为 **trait 对象**(_trait object_),第十七章刚好有一整个部分 [“box 允许创建递归类型”](#enabling-recursive-types-with-boxes) 专门讲解这个主题。所以这里所学的内容会在第十七章再次用上!
|
||||
|
||||
### 使用 `Box<T>` 在堆上储存数据
|
||||
|
||||
@ -22,10 +22,7 @@
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let b = Box::new(5);
|
||||
println!("b = {}", b);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-01/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-1:使用 box 在堆上储存一个 `i32` 值</span>
|
||||
@ -55,10 +52,7 @@ cons list 的每一项都包含两个元素:当前项的值和下一项。其
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
enum List {
|
||||
Cons(i32, List),
|
||||
Nil,
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-02/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-2:第一次尝试定义一个代表 `i32` 值的 cons list 数据结构的枚举</span>
|
||||
@ -69,12 +63,8 @@ enum List {
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
use crate::List::{Cons, Nil};
|
||||
|
||||
fn main() {
|
||||
let list = Cons(1, Cons(2, Cons(3, Nil)));
|
||||
}
|
||||
```rust,ignore,does_not_compile
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-03/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-3:使用 `List` 枚举储存列表 `1, 2, 3`</span>
|
||||
@ -83,17 +73,8 @@ fn main() {
|
||||
|
||||
如果尝试编译示例 15-3 的代码,会得到如示例 15-4 所示的错误:
|
||||
|
||||
```text
|
||||
error[E0072]: recursive type `List` has infinite size
|
||||
--> src/main.rs:1:1
|
||||
|
|
||||
1 | enum List {
|
||||
| ^^^^^^^^^ recursive type has infinite size
|
||||
2 | Cons(i32, List),
|
||||
| ----- recursive without indirection
|
||||
|
|
||||
= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
|
||||
make `List` representable
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-03/output.txt}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-4:尝试定义一个递归枚举时得到的错误</span>
|
||||
@ -105,12 +86,7 @@ error[E0072]: recursive type `List` has infinite size
|
||||
回忆一下第六章讨论枚举定义时示例 6-2 中定义的 `Message` 枚举:
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
Move { x: i32, y: i32 },
|
||||
Write(String),
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-02/src/main.rs:here}}
|
||||
```
|
||||
|
||||
当 Rust 需要知道要为 `Message` 值分配多少空间时,它可以检查每一个成员并发现 `Message::Quit` 并不需要任何空间,`Message::Move` 需要足够储存两个 `i32` 值的空间,依此类推。因为 enum 实际上只会使用其中的一个成员,所以 `Message` 值所需的空间等于储存其最大成员的空间大小。
|
||||
@ -126,8 +102,10 @@ enum Message {
|
||||
Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了示例 15-4 中的错误。这个错误也包括了有用的建议:
|
||||
|
||||
```text
|
||||
= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to
|
||||
make `List` representable
|
||||
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
|
||||
|
|
||||
2 | Cons(i32, Box<List>),
|
||||
| ^^^^ ^
|
||||
```
|
||||
|
||||
在建议中,“indirection” 意味着不同于直接储存一个值,我们将间接的储存一个指向值的指针。
|
||||
@ -139,19 +117,7 @@ Rust 无法计算出要为定义为递归的类型分配多少空间,所以编
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
enum List {
|
||||
Cons(i32, Box<List>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
use crate::List::{Cons, Nil};
|
||||
|
||||
fn main() {
|
||||
let list = Cons(1,
|
||||
Box::new(Cons(2,
|
||||
Box::new(Cons(3,
|
||||
Box::new(Nil))))));
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-05/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-5:为了拥有已知大小而使用 `Box<T>` 的 `List` 定义</span>
|
||||
@ -165,3 +131,5 @@ fn main() {
|
||||
box 只提供了间接存储和堆分配;他们并没有任何其他特殊的功能,比如我们将会见到的其他智能指针。它们也没有这些特殊功能带来的性能损失,所以他们可以用于像 cons list 这样间接存储是唯一所需功能的场景。我们还将在第十七章看到 box 的更多应用场景。
|
||||
|
||||
`Box<T>` 类型是一个智能指针,因为它实现了 `Deref` trait,它允许 `Box<T>` 值被当作引用对待。当 `Box<T>` 值离开作用域时,由于 `Box<T>` 类型 `Drop` trait 的实现,box 所指向的堆数据也会被清除。让我们更详细的探索一下这两个 trait。这两个 trait 对于在本章余下讨论的其他智能指针所提供的功能中,将会更为重要。
|
||||
|
||||
[trait-objects]: ch17-02-trait-objects.html#为使用不同类型的值而设计的-trait-对象
|
||||
|
@ -1,7 +1,7 @@
|
||||
## 通过 `Deref` trait 将智能指针当作常规引用处理
|
||||
|
||||
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/main/src/ch15-02-deref.md) <br>
|
||||
> commit 44f1b71c117b0dcec7805eced0b95405167092f6
|
||||
> commit 5bebc80f61d33438f5598c1f7a20cc16be88ed08
|
||||
|
||||
实现 `Deref` trait 允许我们重载 **解引用运算符**(_dereference operator_)`*`(与乘法运算符或通配符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
|
||||
|
||||
@ -16,13 +16,7 @@
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = &x;
|
||||
|
||||
assert_eq!(5, x);
|
||||
assert_eq!(5, *y);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-06/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-6:使用解引用运算符来跟踪 `i32` 值的引用</span>
|
||||
@ -31,15 +25,8 @@ fn main() {
|
||||
|
||||
相反如果尝试编写 `assert_eq!(5, y);`,则会得到如下编译错误:
|
||||
|
||||
```text
|
||||
error[E0277]: can't compare `{integer}` with `&{integer}`
|
||||
--> src/main.rs:6:5
|
||||
|
|
||||
6 | assert_eq!(5, y);
|
||||
| ^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
|
||||
|
|
||||
= help: the trait `std::cmp::PartialEq<&{integer}>` is not implemented for
|
||||
`{integer}`
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/output-only-01-comparing-to-reference/output.txt}}
|
||||
```
|
||||
|
||||
不允许比较数字的引用与数字,因为它们是不同的类型。必须使用解引用运算符追踪引用所指向的值。
|
||||
@ -51,18 +38,12 @@ error[E0277]: can't compare `{integer}` with `&{integer}`
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = Box::new(x);
|
||||
|
||||
assert_eq!(5, x);
|
||||
assert_eq!(5, *y);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-07/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-7:在 `Box<i32>` 上使用解引用运算符</span>
|
||||
|
||||
示例 15-7 相比示例 15-6 唯一不同的地方就是将 `y` 设置为一个指向 `x` 值的 box 实例,而不是指向 `x` 值的引用。在最后的断言中,可以使用解引用运算符以 `y` 为引用时相同的方式追踪 box 的指针。接下来让我们通过实现自己的 box 类型来探索 `Box<T>` 能这么做有何特殊之处。
|
||||
示例 15-7 相比示例 15-6 主要不同的地方就是将 `y` 设置为一个指向 `x` 值拷贝的 box 实例,而不是指向 `x` 值的引用。在最后的断言中,可以使用解引用运算符以 `y` 为引用时相同的方式追踪 box 的指针。接下来让我们通过实现自己的 box 类型来探索 `Box<T>` 能这么做有何特殊之处。
|
||||
|
||||
### 自定义智能指针
|
||||
|
||||
@ -73,13 +54,7 @@ fn main() {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
struct MyBox<T>(T);
|
||||
|
||||
impl<T> MyBox<T> {
|
||||
fn new(x: T) -> MyBox<T> {
|
||||
MyBox(x)
|
||||
}
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-08/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-8:定义 `MyBox<T>` 类型</span>
|
||||
@ -91,54 +66,34 @@ impl<T> MyBox<T> {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = MyBox::new(x);
|
||||
|
||||
assert_eq!(5, x);
|
||||
assert_eq!(5, *y);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-09/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-9:尝试以使用引用和 `Box<T>` 相同的方式使用 `MyBox<T>`</span>
|
||||
|
||||
得到的编译错误是:
|
||||
|
||||
```text
|
||||
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
|
||||
--> src/main.rs:14:19
|
||||
|
|
||||
14 | assert_eq!(5, *y);
|
||||
| ^^
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-09/output.txt}}
|
||||
```
|
||||
|
||||
`MyBox<T>` 类型不能解引用,因为我们尚未在该类型实现这个功能。为了启用 `*` 运算符的解引用功能,需要实现 `Deref` trait。
|
||||
|
||||
### 通过实现 `Deref` trait 将某类型像引用一样处理
|
||||
|
||||
如第十章所讨论的,为了实现 trait,需要提供 trait 所需的方法实现。`Deref` trait,由标准库提供,要求实现名为 `deref` 的方法,其借用 `self` 并返回一个内部数据的引用。示例 15-10 包含定义于 `MyBox` 之上的 `Deref` 实现:
|
||||
如第十章 [“为类型实现 trait”][impl-trait] 部分所讨论的,为了实现 trait,需要提供 trait 所需的方法实现。`Deref` trait,由标准库提供,要求实现名为 `deref` 的方法,其借用 `self` 并返回一个内部数据的引用。示例 15-10 包含定义于 `MyBox` 之上的 `Deref` 实现:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
use std::ops::Deref;
|
||||
|
||||
# struct MyBox<T>(T);
|
||||
|
||||
impl<T> Deref for MyBox<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-10/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-10:`MyBox<T>` 上的 `Deref` 实现</span>
|
||||
|
||||
`type Target = T;` 语法定义了用于此 trait 的关联类型。关联类型是一个稍有不同的定义泛型参数的方式,现在还无需过多的担心它;第十九章会详细介绍。
|
||||
|
||||
`deref` 方法体中写入了 `&self.0`,这样 `deref` 返回了我希望通过 `*` 运算符访问的值的引用。示例 15-9 中的 `main` 函数中对 `MyBox<T>` 值的 `*` 调用现在可以编译并能通过断言了!
|
||||
`deref` 方法体中写入了 `&self.0`,这样 `deref` 返回了我希望通过 `*` 运算符访问的值的引用。回忆一下第五章 [“使用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分 `.0` 用来访问元组结构体的第一个元素。示例 15-9 中的 `main` 函数中对 `MyBox<T>` 值的 `*` 调用现在可以编译并能通过断言了!
|
||||
|
||||
没有 `Deref` trait 的话,编译器只会解引用 `&` 引用类型。`deref` 方法向编译器提供了获取任何实现了 `Deref` trait 的类型的值,并且调用这个类型的 `deref` 方法来获取一个它知道如何解引用的 `&` 引用的能力。
|
||||
|
||||
@ -156,7 +111,7 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行普通解引用
|
||||
|
||||
### 函数和方法的隐式 Deref 强制转换
|
||||
|
||||
**Deref 强制转换**(_deref coercions_)是 Rust 在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,Deref 强制转换将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
**Deref 强制转换**(_deref coercions_)是 Rust 在函数或方法传参上的一种便利。Deref 强制转换只能作用于实现了 `Deref` trait 的类型。Deref 强制转换将这样一个类型的引用转换为另一个类型的引用。例如,Deref 强制转换 可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref` trait 因此可以返回 `&str`。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,Deref 强制转换将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
|
||||
Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&` 和 `*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
|
||||
|
||||
@ -165,9 +120,7 @@ Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn hello(name: &str) {
|
||||
println!("Hello, {}!", name);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-11/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-11:`hello` 函数有着 `&str` 类型的参数 `name`</span>
|
||||
@ -177,32 +130,7 @@ fn hello(name: &str) {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
# use std::ops::Deref;
|
||||
#
|
||||
# struct MyBox<T>(T);
|
||||
#
|
||||
# impl<T> MyBox<T> {
|
||||
# fn new(x: T) -> MyBox<T> {
|
||||
# MyBox(x)
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# impl<T> Deref for MyBox<T> {
|
||||
# type Target = T;
|
||||
#
|
||||
# fn deref(&self) -> &T {
|
||||
# &self.0
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# fn hello(name: &str) {
|
||||
# println!("Hello, {}!", name);
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let m = MyBox::new(String::from("Rust"));
|
||||
hello(&m);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-12/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-12:因为 Deref 强制转换,使用 `MyBox<String>` 的引用调用 `hello` 是可行的</span>
|
||||
@ -214,32 +142,7 @@ fn main() {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
# use std::ops::Deref;
|
||||
#
|
||||
# struct MyBox<T>(T);
|
||||
#
|
||||
# impl<T> MyBox<T> {
|
||||
# fn new(x: T) -> MyBox<T> {
|
||||
# MyBox(x)
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# impl<T> Deref for MyBox<T> {
|
||||
# type Target = T;
|
||||
#
|
||||
# fn deref(&self) -> &T {
|
||||
# &self.0
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# fn hello(name: &str) {
|
||||
# println!("Hello, {}!", name);
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let m = MyBox::new(String::from("Rust"));
|
||||
hello(&(*m)[..]);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-13/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-13:如果 Rust 没有 Deref 强制转换则必须编写的代码</span>
|
||||
@ -254,10 +157,13 @@ fn main() {
|
||||
|
||||
Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换:
|
||||
|
||||
- 当 `T: Deref<Target=U>` 时从 `&T` 到 `&U`。
|
||||
- 当 `T: DerefMut<Target=U>` 时从 `&mut T` 到 `&mut U`。
|
||||
- 当 `T: Deref<Target=U>` 时从 `&mut T` 到 `&U`。
|
||||
* 当 `T: Deref<Target=U>` 时从 `&T` 到 `&U`。
|
||||
* 当 `T: DerefMut<Target=U>` 时从 `&mut T` 到 `&mut U`。
|
||||
* 当 `T: Deref<Target=U>` 时从 `&mut T` 到 `&U`。
|
||||
|
||||
头两个情况除了可变性之外是相同的:第一种情况表明如果有一个 `&T`,而 `T` 实现了返回 `U` 类型的 `Deref`,则可以直接得到 `&U`。第二种情况表明对于可变引用也有着相同的行为。
|
||||
|
||||
第三个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是 **不可能** 的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要数据只能有一个不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
|
||||
第三个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是 **不可能** 的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
|
||||
|
||||
[impl-trait]: ch10-02-traits.html#implementing-a-trait-on-a-type
|
||||
[tuple-structs]: ch05-01-defining-structs.html#使用没有命名字段的元组结构体来创建不同的类型
|
||||
|
@ -1,9 +1,9 @@
|
||||
## 使用 `Drop` Trait 运行清理代码
|
||||
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/main/src/ch15-03-drop.md) <br>
|
||||
> commit 57adb83f69a69e20862d9e107b2a8bab95169b4c
|
||||
> commit d44317c3122b44fb713aba66cc295dee3453b24b
|
||||
|
||||
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box<T>` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
|
||||
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。
|
||||
|
||||
在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。
|
||||
|
||||
@ -14,21 +14,7 @@
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
struct CustomSmartPointer {
|
||||
data: String,
|
||||
}
|
||||
|
||||
impl Drop for CustomSmartPointer {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let c = CustomSmartPointer { data: String::from("my stuff") };
|
||||
let d = CustomSmartPointer { data: String::from("other stuff") };
|
||||
println!("CustomSmartPointers created.");
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-14/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-14:结构体 `CustomSmartPointer`,其实现了放置清理代码的 `Drop` trait</span>
|
||||
@ -39,10 +25,8 @@ fn main() {
|
||||
|
||||
当运行这个程序,会出现如下输出:
|
||||
|
||||
```text
|
||||
CustomSmartPointers created.
|
||||
Dropping CustomSmartPointer with data `other stuff`!
|
||||
Dropping CustomSmartPointer with data `my stuff`!
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-14/output.txt}}
|
||||
```
|
||||
|
||||
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创建时相反的顺序被丢弃,所以 `d` 在 `c` 之前被丢弃。这个例子刚好给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
|
||||
@ -56,24 +40,15 @@ Dropping CustomSmartPointer with data `my stuff`!
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
fn main() {
|
||||
let c = CustomSmartPointer { data: String::from("some data") };
|
||||
println!("CustomSmartPointer created.");
|
||||
c.drop();
|
||||
println!("CustomSmartPointer dropped before the end of main.");
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-15/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-15:尝试手动调用 `Drop` trait 的 `drop` 方法提早清理</span>
|
||||
|
||||
如果尝试编译代码会得到如下错误:
|
||||
|
||||
```text
|
||||
error[E0040]: explicit use of destructor method
|
||||
--> src/main.rs:14:7
|
||||
|
|
||||
14 | c.drop();
|
||||
| ^^^^ explicit destructor calls not allowed
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-15/output.txt}}
|
||||
```
|
||||
|
||||
错误信息表明不允许显式调用 `drop`。错误信息使用了术语 **析构函数**(_destructor_),这是一个清理实例的函数的通用编程概念。**析构函数** 对应创建实例的 **构造函数**。Rust 中的 `drop` 函数就是这么一个析构函数。
|
||||
@ -87,32 +62,15 @@ Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
# struct CustomSmartPointer {
|
||||
# data: String,
|
||||
# }
|
||||
#
|
||||
# impl Drop for CustomSmartPointer {
|
||||
# fn drop(&mut self) {
|
||||
# println!("Dropping CustomSmartPointer with data `{}`!", self.data);
|
||||
# }
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let c = CustomSmartPointer { data: String::from("some data") };
|
||||
println!("CustomSmartPointer created.");
|
||||
drop(c);
|
||||
println!("CustomSmartPointer dropped before the end of main.");
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-16/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-16: 在值离开作用域之前调用 `std::mem::drop` 显式清理</span>
|
||||
|
||||
运行这段代码会打印出如下:
|
||||
|
||||
```text
|
||||
CustomSmartPointer created.
|
||||
Dropping CustomSmartPointer with data `some data`!
|
||||
CustomSmartPointer dropped before the end of main.
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-16/output.txt}}
|
||||
```
|
||||
|
||||
`` Dropping CustomSmartPointer with data `some data`! `` 出现在 `CustomSmartPointer created.` 和 `CustomSmartPointer dropped before the end of main.` 之间,表明了 `drop` 方法被调用了并在此丢弃了 `c`。
|
||||
|
@ -1,7 +1,7 @@
|
||||
## `Rc<T>` 引用计数智能指针
|
||||
|
||||
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/main/src/ch15-04-rc.md) <br>
|
||||
> commit 6f292c8439927b4c5b870dd4afd2bfc52cc4eccc
|
||||
> commit 45fe0fc9af98a214ed779d2cfac6773bdbfc708e
|
||||
|
||||
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理。
|
||||
|
||||
@ -28,61 +28,27 @@
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
enum List {
|
||||
Cons(i32, Box<List>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
use crate::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));
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-17/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-17: 展示不能用两个 `Box<T>` 的列表尝试共享第三个列表的所有权</span>
|
||||
|
||||
编译会得出如下错误:
|
||||
|
||||
```text
|
||||
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
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-17/output.txt}}
|
||||
```
|
||||
|
||||
`Cons` 成员拥有其储存的数据,所以当创建 `b` 列表时,`a` 被移动进了 `b` 这样 `b` 就拥有了 `a`。接着当再次尝试使用 `a` 创建 `c` 时,这不被允许,因为 `a` 的所有权已经被移动。
|
||||
|
||||
可以改变 `Cons` 的定义来存放一个引用,不过接着必须指定生命周期参数。通过指定生命周期参数,表明列表中的每一个元素都至少与列表本身存在的一样久。例如,借用检查器不会允许 `let a = Cons(10, &Nil);` 编译,因为临时值 `Nil` 会在 `a` 获取其引用之前就被丢弃了。
|
||||
可以改变 `Cons` 的定义来存放一个引用,不过接着必须指定生命周期参数。通过指定生命周期参数,表明列表中的每一个元素都至少与列表本身存在的一样久。这是示例 15-17 中元素与列表的情况,但并不是所有情况都如此。
|
||||
|
||||
相反,我们修改 `List` 的定义为使用 `Rc<T>` 代替 `Box<T>`,如列表 15-18 所示。现在每一个 `Cons` 变量都包含一个值和一个指向 `List` 的 `Rc<T>`。当创建 `b` 时,不同于获取 `a` 的所有权,这里会克隆 `a` 所包含的 `Rc<List>`,这会将引用计数从 1 增加到 2 并允许 `a` 和 `b` 共享 `Rc<List>` 中数据的所有权。创建 `c` 时也会克隆 `a`,这会将引用计数从 2 增加为 3。每次调用 `Rc::clone`,`Rc<List>` 中数据的引用计数都会增加,直到有零个引用之前其数据都不会被清理。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
enum List {
|
||||
Cons(i32, Rc<List>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
use crate::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, Rc::clone(&a));
|
||||
let c = Cons(4, Rc::clone(&a));
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-18/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-18: 使用 `Rc<T>` 定义的 `List`</span>
|
||||
@ -100,25 +66,7 @@ fn main() {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
# enum List {
|
||||
# Cons(i32, Rc<List>),
|
||||
# Nil,
|
||||
# }
|
||||
#
|
||||
# use crate::List::{Cons, Nil};
|
||||
# use std::rc::Rc;
|
||||
#
|
||||
fn main() {
|
||||
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
|
||||
println!("count after creating a = {}", Rc::strong_count(&a));
|
||||
let b = Cons(3, Rc::clone(&a));
|
||||
println!("count after creating b = {}", Rc::strong_count(&a));
|
||||
{
|
||||
let c = Cons(4, Rc::clone(&a));
|
||||
println!("count after creating c = {}", Rc::strong_count(&a));
|
||||
}
|
||||
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-19/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-19:打印出引用计数</span>
|
||||
@ -127,11 +75,8 @@ fn main() {
|
||||
|
||||
这段代码会打印出:
|
||||
|
||||
```text
|
||||
count after creating a = 1
|
||||
count after creating b = 2
|
||||
count after creating c = 3
|
||||
count after c goes out of scope = 2
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}}
|
||||
```
|
||||
|
||||
我们能够看到 `a` 中 `Rc<List>` 的初始引用计数为1,接着每次调用 `clone`,计数会增加1。当 `c` 离开作用域时,计数减1。不必像调用 `Rc::clone` 增加引用计数那样调用一个函数来减少计数;`Drop` trait 的实现当 `Rc<T>` 值离开作用域时自动减少引用计数。
|
||||
|
@ -1,7 +1,7 @@
|
||||
## `RefCell<T>` 和内部可变性模式
|
||||
|
||||
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch15-05-interior-mutability.md) <br>
|
||||
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
|
||||
> commit 74edb8dfe07edf8fdae49c6385c72840c07dd18f
|
||||
|
||||
**内部可变性**(_Interior mutability_)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
|
||||
@ -37,22 +37,13 @@
|
||||
借用规则的一个推论是当有一个不可变值时,不能可变地借用它。例如,如下代码不能编译:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = &mut x;
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/no-listing-01-cant-borrow-immutable-as-mutable/src/main.rs}}
|
||||
```
|
||||
|
||||
如果尝试编译,会得到如下错误:
|
||||
|
||||
```text
|
||||
error[E0596]: cannot borrow immutable local variable `x` as mutable
|
||||
--> src/main.rs:3:18
|
||||
|
|
||||
2 | let x = 5;
|
||||
| - consider changing this to `mut x`
|
||||
3 | let y = &mut x;
|
||||
| ^ cannot borrow mutably
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/no-listing-01-cant-borrow-immutable-as-mutable/output.txt}}
|
||||
```
|
||||
|
||||
然而,特定情况下,令一个值在其方法内部能够修改自身,而在其他代码中仍视为不可变,是很有用的。值方法外部的代码就不能修改其值了。`RefCell<T>` 是一个获得内部可变性的方法。`RefCell<T>` 并没有完全绕开借用规则,编译器中的借用检查器允许内部可变性并相应地在运行时检查借用规则。如果违反了这些规则,会出现 panic 而不是编译错误。
|
||||
@ -71,82 +62,20 @@ error[E0596]: cannot borrow immutable local variable `x` as mutable
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
pub trait Messenger {
|
||||
fn send(&self, msg: &str);
|
||||
}
|
||||
|
||||
pub struct LimitTracker<'a, T: Messenger> {
|
||||
messenger: &'a T,
|
||||
value: usize,
|
||||
max: usize,
|
||||
}
|
||||
|
||||
impl<'a, T> LimitTracker<'a, T>
|
||||
where T: Messenger {
|
||||
pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
|
||||
LimitTracker {
|
||||
messenger,
|
||||
value: 0,
|
||||
max,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, value: usize) {
|
||||
self.value = value;
|
||||
|
||||
let percentage_of_max = self.value as f64 / self.max as f64;
|
||||
|
||||
if percentage_of_max >= 1.0 {
|
||||
self.messenger.send("Error: You are over your quota!");
|
||||
} else if percentage_of_max >= 0.9 {
|
||||
self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
|
||||
} else if percentage_of_max >= 0.75 {
|
||||
self.messenger.send("Warning: You've used up over 75% of your quota!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-20/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-20:一个记录某个值与最大值差距的库,并根据此值的特定级别发出警告</span>
|
||||
|
||||
这些代码中一个重要部分是拥有一个方法 `send` 的 `Messenger` trait,其获取一个 `self` 的不可变引用和文本信息。这是我们的 mock 对象所需要拥有的接口。另一个重要的部分是我们需要测试 `LimitTracker` 的 `set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。
|
||||
这些代码中一个重要部分是拥有一个方法 `send` 的 `Messenger` trait,其获取一个 `self` 的不可变引用和文本信息。这个 trait 是 mock 对象所需要实现的接口库,这样 mock 就能像一个真正的对象那样使用了。另一个重要的部分是我们需要测试 `LimitTracker` 的 `set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。
|
||||
|
||||
我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象实例,用其创建 `LimitTracker`,调用 `LimitTracker` 的 `set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct MockMessenger {
|
||||
sent_messages: Vec<String>,
|
||||
}
|
||||
|
||||
impl MockMessenger {
|
||||
fn new() -> MockMessenger {
|
||||
MockMessenger { sent_messages: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Messenger for MockMessenger {
|
||||
fn send(&self, message: &str) {
|
||||
self.sent_messages.push(String::from(message));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_sends_an_over_75_percent_warning_message() {
|
||||
let mock_messenger = MockMessenger::new();
|
||||
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
|
||||
|
||||
limit_tracker.set_value(80);
|
||||
|
||||
assert_eq!(mock_messenger.sent_messages.len(), 1);
|
||||
}
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-21/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-21:尝试实现 `MockMessenger`,借用检查器不允许这么做</span>
|
||||
@ -157,14 +86,8 @@ mod tests {
|
||||
|
||||
然而,这个测试是有问题的:
|
||||
|
||||
```text
|
||||
error[E0596]: cannot borrow immutable field `self.sent_messages` as mutable
|
||||
--> src/lib.rs:52:13
|
||||
|
|
||||
51 | fn send(&self, message: &str) {
|
||||
| ----- use `&mut self` here to make mutable
|
||||
52 | self.sent_messages.push(String::from(message));
|
||||
| ^^^^^^^^^^^^^^^^^^ cannot mutably borrow immutable field
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-21/output.txt}}
|
||||
```
|
||||
|
||||
不能修改 `MockMessenger` 来记录消息,因为 `send` 方法获取了 `self` 的不可变引用。我们也不能参考错误文本的建议使用 `&mut self` 替代,因为这样 `send` 的签名就不符合 `Messenger` trait 定义中的签名了(可以试着这么改,看看会出现什么错误信息)。
|
||||
@ -173,74 +96,8 @@ error[E0596]: cannot borrow immutable field `self.sent_messages` as mutable
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub trait Messenger {
|
||||
# fn send(&self, msg: &str);
|
||||
# }
|
||||
#
|
||||
# pub struct LimitTracker<'a, T: Messenger> {
|
||||
# messenger: &'a T,
|
||||
# value: usize,
|
||||
# max: usize,
|
||||
# }
|
||||
#
|
||||
# impl<'a, T> LimitTracker<'a, T>
|
||||
# where T: Messenger {
|
||||
# pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
|
||||
# LimitTracker {
|
||||
# messenger,
|
||||
# value: 0,
|
||||
# max,
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# pub fn set_value(&mut self, value: usize) {
|
||||
# self.value = value;
|
||||
#
|
||||
# let percentage_of_max = self.value as f64 / self.max as f64;
|
||||
#
|
||||
# if percentage_of_max >= 1.0 {
|
||||
# self.messenger.send("Error: You are over your quota!");
|
||||
# } else if percentage_of_max >= 0.9 {
|
||||
# self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
|
||||
# } else if percentage_of_max >= 0.75 {
|
||||
# self.messenger.send("Warning: You've used up over 75% of your quota!");
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::RefCell;
|
||||
|
||||
struct MockMessenger {
|
||||
sent_messages: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl MockMessenger {
|
||||
fn new() -> MockMessenger {
|
||||
MockMessenger { sent_messages: RefCell::new(vec![]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Messenger for MockMessenger {
|
||||
fn send(&self, message: &str) {
|
||||
self.sent_messages.borrow_mut().push(String::from(message));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_sends_an_over_75_percent_warning_message() {
|
||||
// --snip--
|
||||
# let mock_messenger = MockMessenger::new();
|
||||
# let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
|
||||
# limit_tracker.set_value(75);
|
||||
|
||||
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
|
||||
}
|
||||
}
|
||||
# fn main() {}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-22/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-22:使用 `RefCell<T>` 能够在外部值被认为是不可变的情况下修改内部值</span>
|
||||
@ -264,26 +121,15 @@ mod tests {
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore,panics
|
||||
impl Messenger for MockMessenger {
|
||||
fn send(&self, message: &str) {
|
||||
let mut one_borrow = self.sent_messages.borrow_mut();
|
||||
let mut two_borrow = self.sent_messages.borrow_mut();
|
||||
|
||||
one_borrow.push(String::from(message));
|
||||
two_borrow.push(String::from(message));
|
||||
}
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-23/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-23:在同一作用域中创建两个可变引用并观察 `RefCell<T>` panic</span>
|
||||
|
||||
这里为 `borrow_mut` 返回的 `RefMut` 智能指针创建了 `one_borrow` 变量。接着用相同的方式在变量 `two_borrow` 创建了另一个可变借用。这会在相同作用域中创建两个可变引用,这是不允许的。当运行库的测试时,示例 15-23 编译时不会有任何错误,不过测试会失败:
|
||||
|
||||
```text
|
||||
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
|
||||
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at
|
||||
'already borrowed: BorrowMutError', src/libcore/result.rs:906:4
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-23/output.txt}}
|
||||
```
|
||||
|
||||
注意代码 panic 和信息 `already borrowed: BorrowMutError`。这也就是 `RefCell<T>` 如何在运行时处理违反借用规则的情况。
|
||||
@ -299,30 +145,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
enum List {
|
||||
Cons(Rc<RefCell<i32>>, Rc<List>),
|
||||
Nil,
|
||||
}
|
||||
|
||||
use crate::List::{Cons, Nil};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
fn main() {
|
||||
let value = Rc::new(RefCell::new(5));
|
||||
|
||||
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
|
||||
|
||||
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
|
||||
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
|
||||
|
||||
*value.borrow_mut() += 10;
|
||||
|
||||
println!("a after = {:?}", a);
|
||||
println!("b after = {:?}", b);
|
||||
println!("c after = {:?}", c);
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-24/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-24:使用 `Rc<RefCell<i32>>` 创建可以修改的 `List`</span>
|
||||
@ -335,14 +158,12 @@ fn main() {
|
||||
|
||||
当我们打印出 `a`、`b` 和 `c` 时,可以看到他们都拥有修改后的值 15 而不是 5:
|
||||
|
||||
```text
|
||||
a after = Cons(RefCell { value: 15 }, Nil)
|
||||
b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
|
||||
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
|
||||
```console
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-24/output.txt}}
|
||||
```
|
||||
|
||||
这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。
|
||||
|
||||
标准库中也有其他提供内部可变性的类型,比如 `Cell<T>`,它类似 `RefCell<T>` 但有一点除外:它并非提供内部值的引用,而是把值拷贝进和拷贝出 `Cell<T>`。还有 `Mutex<T>`,其提供线程间安全的内部可变性,我们将在第 16 章中讨论其用法。请查看标准库来获取更多细节关于这些不同类型之间的区别。
|
||||
|
||||
[wheres-the---operator]: ch05-03-method-syntax.html#wheres-the---operator
|
||||
[wheres-the---operator]: ch05-03-method-syntax.html#--运算符到哪去了
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user