mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
update ch15
This commit is contained in:
parent
b05c60534c
commit
7dc059f009
@ -2,17 +2,15 @@
|
||||
|
||||
> [ch15-00-smart-pointers.md](https://github.com/rust-lang/book/blob/main/src/ch15-00-smart-pointers.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
|
||||
|
||||
**指针** (*pointer*)是一个包含内存地址的变量的通用概念。这个地址引用,或 “指向”(points at)一些其他数据。Rust 中最常见的指针是第四章介绍的 **引用**(*reference*)。引用以 `&` 符号为标志并借用了他们所指向的值。除了引用数据没有任何其他特殊功能。它们也没有任何额外开销,所以应用得最多。
|
||||
**指针** (*pointer*)是一个包含内存地址的变量的通用概念。这个地址引用,或 “指向”(points at)一些其他数据。Rust 中最常见的指针是第四章介绍的 **引用**(*reference*)。引用以 `&` 符号为标志并借用了他们所指向的值。除了引用数据没有任何其他特殊功能,也没有额外开销。
|
||||
|
||||
另一方面,**智能指针**(*smart pointers*)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能。智能指针的概念并不为 Rust 所独有;其起源于 C++ 并存在于其他语言中。Rust 标准库中不同的智能指针提供了多于引用的额外功能。本章将会探索的一个例子便是 **引用计数** (*reference counting*)智能指针类型,其允许数据有多个所有者。引用计数智能指针记录总共有多少个所有者,并当没有任何所有者时负责清理数据。
|
||||
另一方面,**智能指针**(*smart pointers*)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能。智能指针的概念并不为 Rust 所独有;其起源于 C++ 并存在于其他语言中。Rust 标准库中定义了多种不同的智能指针,它们提供了多于引用的额外功能。为了探索其基本概念,我们来看看一些智能指针的例子,这包括 **引用计数** (*reference counting*)智能指针类型。这种指针许数据有多个所有者,它会记录所有者的数量,当没有所有者时清理数据。在 Rust 中因为引用和借用,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反,在大部分情况下,智能指针 **拥有** 他们指向的数据。
|
||||
|
||||
在 Rust 中,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反,在大部分情况下,智能指针 **拥有** 他们指向的数据。
|
||||
虽然当时没有这么称呼它们,实际上本书中已经出现过一些智能指针,比如第八章的 `String` 和 `Vec<T>`,虽然当时我们并不这么称呼它们。这些类型都属于智能指针因为它们拥有一些数据并允许你修改它们。它们也拥有元数据和额外的功能或保证。例如 `String` 存储了其容量作为元数据,并拥有额外的能力确保其数据总是有效的 UTF-8 编码。
|
||||
|
||||
实际上本书中已经出现过一些智能指针,比如第八章的 `String` 和 `Vec<T>`,虽然当时我们并不这么称呼它们。这些类型都属于智能指针因为它们拥有一些数据并允许你修改它们。它们也带有元数据(比如他们的容量)和额外的功能或保证(`String` 的数据总是有效的 UTF-8 编码)。
|
||||
|
||||
智能指针通常使用结构体实现。智能指针区别于常规结构体的显著特性在于其实现了 `Deref` 和 `Drop` trait。`Deref` trait 允许智能指针结构体实例表现的像引用一样,这样就可以编写既用于引用、又用于智能指针的代码。`Drop` trait 允许我们自定义当智能指针离开作用域时运行的代码。本章会讨论这些 trait 以及为什么对于智能指针来说他们很重要。
|
||||
智能指针通常使用结构体实现。智能指针不同于结构体的地方在于其实现了 `Deref` 和 `Drop` trait。`Deref` trait 允许智能指针结构体实例表现的像引用一样,这样就可以编写既用于引用、又用于智能指针的代码。`Drop` trait 允许我们自定义当智能指针离开作用域时运行的代码。本章会讨论这些 trait 以及为什么对于智能指针来说他们很重要。
|
||||
|
||||
考虑到智能指针是一个在 Rust 经常被使用的通用设计模式,本章并不会覆盖所有现存的智能指针。很多库都有自己的智能指针而你也可以编写属于你自己的智能指针。这里将会讲到的是来自标准库中最常用的一些:
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
## 使用`Box<T>`指向堆上的数据
|
||||
|
||||
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/main/src/ch15-01-box.md) <br>
|
||||
> commit 359895c6b2e440275a663ee1a3c17e6a94fdc62b
|
||||
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/main/src/ch15-01-box.md)
|
||||
> <br>
|
||||
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
|
||||
|
||||
最简单直接的智能指针是 _box_,其类型是 `Box<T>`。box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
|
||||
|
||||
@ -15,7 +16,7 @@
|
||||
|
||||
### 使用 `Box<T>` 在堆上储存数据
|
||||
|
||||
在讨论 `Box<T>` 的用例之前,让我们熟悉一下语法以及如何与储存在 `Box<T>` 中的值进行交互。
|
||||
在讨论 `Box<T>` 的堆存储用例之前,让我们熟悉一下语法以及如何与储存在 `Box<T>` 中的值进行交互。
|
||||
|
||||
示例 15-1 展示了如何使用 box 在堆上储存一个 `i32`:
|
||||
|
||||
@ -33,19 +34,23 @@
|
||||
|
||||
### Box 允许创建递归类型
|
||||
|
||||
Rust 需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是 **递归类型**(_recursive type_),其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
|
||||
**递归类型**(_recursive type_)的值可以拥有另一个同类型的值作为其的一部分。这会产生一个问题因为 Rust 需要在编译时知道类型占用多少空间。递归类型的值嵌套理论上可以无限的进行下去,所以 Rust 不知道递归类型需要多少空间。因为 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
|
||||
|
||||
让我们探索一下 _cons list_,一个函数式编程语言中的常见类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
|
||||
作为一个递归类型的例子,让我们探索一下 _cons list_。这是一个函数式编程语言中常见的数据类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
|
||||
|
||||
#### cons list 的更多内容
|
||||
|
||||
_cons list_ 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,`cons` 函数(“construct function" 的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
|
||||
_cons list_ 是一个来源于 Lisp 编程语言及其方言的数据结构,它由嵌套的列表组成。它的名字来源于 Lisp 中的 `cons` 函数(“construct function" 的缩写),它利用两个参数来构造一个新的列表。通过对一个包含值的列表和另一个值调用 `cons`,可以构建由递归列表组成的 cons list。
|
||||
|
||||
cons 函数的概念涉及到更常见的函数式编程术语;“将 _x_ 与 _y_ 连接” 通常意味着构建一个新的容器而将 _x_ 的元素放在新容器的开头,其后则是容器 _y_ 的元素。
|
||||
例如这里有一个包含列表 1,2,3 的 cons list 的伪代码表示,其每一个列表在一个括号中:
|
||||
|
||||
```text
|
||||
(1, (2, (3, Nil)))
|
||||
```
|
||||
|
||||
cons list 的每一项都包含两个元素:当前项的值和下一项。其最后一项值包含一个叫做 `Nil` 的值且没有下一项。cons list 通过递归调用 `cons` 函数产生。代表递归的终止条件(base case)的规范名称是 `Nil`,它宣布列表的终止。注意这不同于第六章中的 “null” 或 “nil” 的概念,他们代表无效或缺失的值。
|
||||
|
||||
注意虽然函数式编程语言经常使用 cons list,但是它并不是一个 Rust 中常见的类型。大部分在 Rust 中需要列表的时候,`Vec<T>` 是一个更好的选择。其他更为复杂的递归数据类型 **确实** 在 Rust 的很多场景中很有用,不过通过以 cons list 作为开始,我们可以探索如何使用 box 毫不费力的定义一个递归数据类型。
|
||||
cons list 并不是一个 Rust 中常见的类型。大部分在 Rust 中需要列表的时候,`Vec<T>` 是一个更好的选择。其他更为复杂的递归数据类型 **确实** 在 Rust 的很多场景中很有用,不过通过以 cons list 作为开始,我们可以探索如何使用 box 毫不费力的定义一个递归数据类型。
|
||||
|
||||
示例 15-2 包含一个 cons list 的枚举定义。注意这还不能编译因为这个类型没有已知的大小,之后我们会展示:
|
||||
|
||||
@ -79,7 +84,7 @@ cons list 的每一项都包含两个元素:当前项的值和下一项。其
|
||||
|
||||
<span class="caption">示例 15-4:尝试定义一个递归枚举时得到的错误</span>
|
||||
|
||||
这个错误表明这个类型 “有无限的大小”。其原因是 `List` 的一个成员被定义为是递归的:它直接存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放 `List` 值到底需要多少空间。让我们一点一点来看:首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。
|
||||
这个错误表明这个类型 “有无限的大小”。其原因是 `List` 的一个成员被定义为是递归的:它直接存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放 `List` 值到底需要多少空间。让我们拆看来看为何会得到这个错误。首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。
|
||||
|
||||
### 计算非递归类型的大小
|
||||
|
||||
@ -99,16 +104,16 @@ cons list 的每一项都包含两个元素:当前项的值和下一项。其
|
||||
|
||||
### 使用 `Box<T>` 给递归类型一个已知的大小
|
||||
|
||||
Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了示例 15-4 中的错误。这个错误也包括了有用的建议:
|
||||
因为 Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了一个包括了有用建议的错误:
|
||||
|
||||
```text
|
||||
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
|
||||
|
|
||||
2 | Cons(i32, Box<List>),
|
||||
| ^^^^ ^
|
||||
| ++++ +
|
||||
```
|
||||
|
||||
在建议中,“indirection” 意味着不同于直接储存一个值,我们将间接的储存一个指向值的指针。
|
||||
在建议中,“indirection” 意味着不同于直接储存一个值,应该间接的储存一个指向值的指针。
|
||||
|
||||
因为 `Box<T>` 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变。这意味着可以将 `Box` 放入 `Cons` 成员中而不是直接存放另一个 `List` 值。`Box` 会指向另一个位于堆上的 `List` 值,而不是存放在 `Cons` 成员中。从概念上讲,我们仍然有一个通过在其中 “存放” 其他列表创建的列表,不过现在实现这个概念的方式更像是一个项挨着另一项,而不是一项包含另一项。
|
||||
|
||||
@ -130,6 +135,6 @@ help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` repre
|
||||
|
||||
box 只提供了间接存储和堆分配;他们并没有任何其他特殊的功能,比如我们将会见到的其他智能指针。它们也没有这些特殊功能带来的性能损失,所以他们可以用于像 cons list 这样间接存储是唯一所需功能的场景。我们还将在第十七章看到 box 的更多应用场景。
|
||||
|
||||
`Box<T>` 类型是一个智能指针,因为它实现了 `Deref` trait,它允许 `Box<T>` 值被当作引用对待。当 `Box<T>` 值离开作用域时,由于 `Box<T>` 类型 `Drop` trait 的实现,box 所指向的堆数据也会被清除。让我们更详细的探索一下这两个 trait。这两个 trait 对于在本章余下讨论的其他智能指针所提供的功能中,将会更为重要。
|
||||
`Box<T>` 类型是一个智能指针,因为它实现了 `Deref` trait,它允许 `Box<T>` 值被当作引用对待。当 `Box<T>` 值离开作用域时,由于 `Box<T>` 类型 `Drop` trait 的实现,box 所指向的堆数据也会被清除。这两个 trait 对于在本章余下讨论的其他智能指针所提供的功能中,将会更为重要。让我们更详细的探索一下这两个 trait。
|
||||
|
||||
[trait-objects]: ch17-02-trait-objects.html#为使用不同类型的值而设计的-trait-对象
|
||||
|
@ -1,17 +1,18 @@
|
||||
## 通过 `Deref` trait 将智能指针当作常规引用处理
|
||||
|
||||
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/main/src/ch15-02-deref.md) <br>
|
||||
> commit 5bebc80f61d33438f5598c1f7a20cc16be88ed08
|
||||
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/main/src/ch15-02-deref.md)
|
||||
> <br>
|
||||
> commit 0514b1cf34c2eaab8285f43305c10a87f4ce34a0
|
||||
|
||||
实现 `Deref` trait 允许我们重载 **解引用运算符**(_dereference operator_)`*`(与乘法运算符或通配符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
|
||||
实现 `Deref` trait 允许我们重载 **解引用运算符**(_dereference operator_)`*`(不要与乘法运算符或通配符相混淆)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
|
||||
|
||||
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **Deref 强制转换**(_deref coercions_)功能以及它是如何处理引用或智能指针的。
|
||||
|
||||
> 我们将要构建的 `MyBox<T>` 类型与真正的 `Box<T>` 有一个很大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 `Deref`,所以其数据实际存放在何处,相比其类似指针的行为来说不算重要。
|
||||
|
||||
### 通过解引用运算符追踪指针的值
|
||||
### 追踪指针的值
|
||||
|
||||
常规引用是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在示例 15-6 中,创建了一个 `i32` 值的引用,接着使用解引用运算符来跟踪所引用的数据:
|
||||
常规引用是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在示例 15-6 中,创建了一个 `i32` 值的引用,接着使用解引用运算符来跟踪所引用的值:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
@ -21,7 +22,7 @@
|
||||
|
||||
<span class="caption">示例 15-6:使用解引用运算符来跟踪 `i32` 值的引用</span>
|
||||
|
||||
变量 `x` 存放了一个 `i32` 值 `5`。`y` 等于 `x` 的一个引用。可以断言 `x` 等于 `5`。然而,如果希望对 `y` 的值做出断言,必须使用 `*y` 来追踪引用所指向的值(也就是 **解引用**)。一旦解引用了 `y`,就可以访问 `y` 所指向的整型值并可以与 `5` 做比较。
|
||||
变量 `x` 存放了一个 `i32` 值 `5`。`y` 等于 `x` 的一个引用。可以断言 `x` 等于 `5`。然而,如果希望对 `y` 的值做出断言,必须使用 `*y` 来追踪引用所指向的值(也就是 **解引用**),这样编译器就可以比较实际的值了。一旦解引用了 `y`,就可以访问 `y` 所指向的整型值并可以与 `5` 做比较。
|
||||
|
||||
相反如果尝试编写 `assert_eq!(5, y);`,则会得到如下编译错误:
|
||||
|
||||
@ -33,7 +34,7 @@
|
||||
|
||||
### 像引用一样使用 `Box<T>`
|
||||
|
||||
可以使用 `Box<T>` 代替引用来重写示例 15-6 中的代码,解引用运算符也一样能工作,如示例 15-7 所示:
|
||||
可以使用 `Box<T>` 代替引用来重写示例 15-6 中的代码,示例 15-7 中 `Box<T>` 上使用的解引用运算符与示例 15-6 中引用上使用的解引用运算符有着一样的功能:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
@ -43,7 +44,7 @@
|
||||
|
||||
<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<T>` 实例,而不是指向 `x` 值的引用。在最后的断言中,可以使用解引用运算符以 `y` 为引用时相同的方式追踪 `Box<T>` 的指针。接下来让我们通过实现自己的类型来探索 `Box<T>` 能这么做有何特殊之处。
|
||||
|
||||
### 自定义智能指针
|
||||
|
||||
@ -111,7 +112,7 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行普通解引用
|
||||
|
||||
### 函数和方法的隐式 Deref 强制转换
|
||||
|
||||
**Deref 强制转换**(_deref coercions_)是 Rust 在函数或方法传参上的一种便利。Deref 强制转换只能作用于实现了 `Deref` trait 的类型。Deref 强制转换将这样一个类型的引用转换为另一个类型的引用。例如,Deref 强制转换 可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref` trait 因此可以返回 `&str`。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,Deref 强制转换将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
**Deref 强制转换**(_deref coercions_)将实现了 `Deref` trait 的类型的引用转换为另一种类型的引用。例如,Deref 强制转换可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref` trait 因此可以返回 `&str`。Deref 强制转换是 Rust 在函数或方法传参上的一种便利操作,并且只能作用于实现了 `Deref` trait 的类型。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
|
||||
Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&` 和 `*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
|
||||
|
||||
@ -161,7 +162,7 @@ Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制
|
||||
* 当 `T: DerefMut<Target=U>` 时从 `&mut T` 到 `&mut U`。
|
||||
* 当 `T: Deref<Target=U>` 时从 `&mut T` 到 `&U`。
|
||||
|
||||
头两个情况除了可变性之外是相同的:第一种情况表明如果有一个 `&T`,而 `T` 实现了返回 `U` 类型的 `Deref`,则可以直接得到 `&U`。第二种情况表明对于可变引用也有着相同的行为。
|
||||
头两个情况除了第二种实现了可变性之外是相同的:第一种情况表明如果有一个 `&T`,而 `T` 实现了返回 `U` 类型的 `Deref`,则可以直接得到 `&U`。第二种情况表明对于可变引用也有着相同的行为。
|
||||
|
||||
第三个情况有些微妙:Rust 也会将可变引用强转为不可变引用。但是反之是 **不可能** 的:不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,而借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
|
||||
|
||||
|
@ -1,15 +1,18 @@
|
||||
## 使用 `Drop` Trait 运行清理代码
|
||||
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/main/src/ch15-03-drop.md) <br>
|
||||
> commit d44317c3122b44fb713aba66cc295dee3453b24b
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/main/src/ch15-03-drop.md)
|
||||
> <br>
|
||||
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
|
||||
|
||||
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。
|
||||
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。
|
||||
|
||||
在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。
|
||||
我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。
|
||||
|
||||
在其他一些语言中的某些类型,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。
|
||||
|
||||
指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop`。
|
||||
|
||||
示例 15-14 展示了唯一定制功能就是当其实例离开作用域时,打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`。这会演示 Rust 何时运行 `drop` 函数:
|
||||
示例 15-14 展示了唯一定制功能就是当其实例离开作用域时,打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`,这会演示 Rust 何时运行 `drop` 函数:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
@ -19,7 +22,7 @@
|
||||
|
||||
<span class="caption">示例 15-14:结构体 `CustomSmartPointer`,其实现了放置清理代码的 `Drop` trait</span>
|
||||
|
||||
`Drop` trait 包含在 prelude 中,所以无需导入它。我们在 `CustomSmartPointer` 上实现了 `Drop` trait,并提供了一个调用 `println!` 的 `drop` 方法实现。`drop` 函数体是放置任何当类型实例离开作用域时期望运行的逻辑的地方。这里选择打印一些文本以展示 Rust 何时调用 `drop`。
|
||||
`Drop` trait 包含在 prelude 中,所以无需导入它。我们在 `CustomSmartPointer` 上实现了 `Drop` trait,并提供了一个调用 `println!` 的 `drop` 方法实现。`drop` 函数体是放置任何当类型实例离开作用域时期望运行的逻辑的地方。这里选择打印一些文本以可视化地展示 Rust 何时调用 `drop`。
|
||||
|
||||
在 `main` 中,我们新建了两个 `CustomSmartPointer` 实例并打印出了 `CustomSmartPointer created.`。在 `main` 的结尾,`CustomSmartPointer` 的实例会离开作用域,而 Rust 会调用放置于 `drop` 方法中的代码,打印出最后的信息。注意无需显式调用 `drop` 方法:
|
||||
|
||||
@ -29,7 +32,7 @@
|
||||
{{#include ../listings/ch15-smart-pointers/listing-15-14/output.txt}}
|
||||
```
|
||||
|
||||
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创建时相反的顺序被丢弃,所以 `d` 在 `c` 之前被丢弃。这个例子刚好给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
|
||||
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创建时相反的顺序被丢弃,所以 `d` 在 `c` 之前被丢弃。这个例子的作用是给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
|
||||
|
||||
#### 通过 `std::mem::drop` 提早丢弃值
|
||||
|
||||
@ -57,7 +60,7 @@ Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结
|
||||
|
||||
因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显式调用 `drop`,如果我们需要强制提早清理值,可以使用 `std::mem::drop` 函数。
|
||||
|
||||
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude,所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数。如示例 15-16 所示:
|
||||
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望强制丢弃的值作为参数。`std::mem::drop` 位于 prelude,所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数。如示例 15-16 所示:
|
||||
|
||||
<span class="filename">文件名:src/main.rs</span>
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
## `Rc<T>` 引用计数智能指针
|
||||
|
||||
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/main/src/ch15-04-rc.md) <br>
|
||||
> commit 45fe0fc9af98a214ed779d2cfac6773bdbfc708e
|
||||
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/main/src/ch15-04-rc.md)
|
||||
> <br>
|
||||
> commit 52fafaaa8e432e84beaaf4ea80ccba880624effd
|
||||
|
||||
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理。
|
||||
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理因此也没有所有者。
|
||||
|
||||
为了启用多所有权,Rust 有一个叫做 `Rc<T>` 的类型。其名称为 **引用计数**(_reference counting_)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
|
||||
为了启用多所有权需要显式地使用 Rust 类型 `Rc<T>`,其为 **引用计数**(_reference counting_)的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用,就代表没有任何有效引用并可以被清理。
|
||||
|
||||
可以将其想象为客厅中的电视。当一个人进来看电视时,他打开电视。其他人也可以进来看电视。当最后一个人离开房间时,他关掉电视因为它不再被使用了。如果某人在其他人还在看的时候就关掉了电视,正在看电视的人肯定会抓狂的!
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
## `RefCell<T>` 和内部可变性模式
|
||||
|
||||
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch15-05-interior-mutability.md) <br>
|
||||
> commit 74edb8dfe07edf8fdae49c6385c72840c07dd18f
|
||||
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch15-05-interior-mutability.md)
|
||||
> <br>
|
||||
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
|
||||
|
||||
**内部可变性**(_Interior mutability_)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
**内部可变性**(_Interior mutability_)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。不安全代码表明我们在手动检查这些规则而不是让编译器替我们检查。第十九章会更详细地介绍不安全代码。
|
||||
|
||||
当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
|
||||
让我们通过遵循内部可变性模式的 `RefCell<T>` 类型来开始探索。
|
||||
|
||||
@ -52,7 +55,7 @@
|
||||
|
||||
#### 内部可变性的用例:mock 对象
|
||||
|
||||
**测试替身**(_test double_)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
|
||||
有时在测试中程序员会用某个类型替换另一个类型,以便观察特定的行为并断言它是被正确实现的。这个占位符类型被称为 **测试替身**(_test double_)。可以把它想象成电影制作中的 “替身演员”("stunt double"),这是一个龙套进入并替代演员进行一些特定的高难度操作。测试替身在运行测试时替代某个类型。**mock 对象** 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。
|
||||
|
||||
虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。
|
||||
|
||||
@ -134,7 +137,7 @@
|
||||
|
||||
注意代码 panic 和信息 `already borrowed: BorrowMutError`。这也就是 `RefCell<T>` 如何在运行时处理违反借用规则的情况。
|
||||
|
||||
在运行时捕获借用错误而不是编译时意味着将会在开发过程的后期才会发现错误,甚至有可能发布到生产环境才发现;还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用 `RefCell` 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取舍,但是我们可以选择使用 `RefCell<T>` 来获得比常规引用所能提供的更多的功能。
|
||||
像我们这里这样选择在运行时捕获借用错误而不是编译时意味着会发现在开发过程的后期才会发现的潜在错误,甚至有可能发布到生产环境才会发现。还会因为在运行时而不是编译时记录借用而导致少量的运行时性能惩罚。然而,使用 `RefCell` 使得在只允许不可变值的上下文中编写修改自身以记录消息的 mock 对象成为可能。虽然有取舍,但是我们可以选择使用 `RefCell<T>` 来获得比常规引用所能提供的更多的功能。
|
||||
|
||||
### 结合 `Rc<T>` 和 `RefCell<T>` 来拥有多个可变数据所有者
|
||||
|
||||
@ -162,8 +165,6 @@
|
||||
{{#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 章中讨论其用法。请查看标准库来获取更多细节关于这些不同类型之间的区别。
|
||||
这是非常巧妙的!通过使用 `RefCell<T>`,我们可以拥有一个表面上不可变的 `List`,不过可以使用 `RefCell<T>` 中提供内部可变性的方法来在需要时修改数据。`RefCell<T>` 的运行时借用规则检查也确实保护我们免于出现数据竞争——有时为了数据结构的灵活性而付出一些性能是值得的。注意 `RefCell<T>` 不能用于多线程代码!`Mutex<T>` 是一个线程安全版本的 `RefCell<T>` ,我们会在第十六章讨论 `Mutex<T>`。
|
||||
|
||||
[wheres-the---operator]: ch05-03-method-syntax.html#--运算符到哪去了
|
||||
|
@ -1,7 +1,8 @@
|
||||
## 引用循环与内存泄漏
|
||||
|
||||
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md) <br>
|
||||
> commit bd27a8b72336610c9a200f0ca932ffc8b6fb5ee1
|
||||
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md)
|
||||
> <br>
|
||||
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
|
||||
|
||||
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**(_memory leak_)),但并不是不可能。与在编译时拒绝数据竞争不同,Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0,其值也永远不会被丢弃。
|
||||
|
||||
@ -39,7 +40,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。然而,`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" />
|
||||
|
||||
@ -47,7 +48,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
||||
|
||||
如果取消最后 `println!` 的注释并运行程序,Rust 会尝试打印出 `a` 指向 `b` 指向 `a` 这样的循环直到栈溢出。
|
||||
|
||||
这个特定的例子中,创建了引用循环之后程序立刻就结束了。这个循环的结果并不可怕。如果在更为复杂的程序中并在循环里分配了很多内存并占有很长时间,这个程序会使用多于它所需要的内存,并有可能压垮系统并造成没有内存可供使用。
|
||||
相比真实世界的程序,这个例子中创建引用循环的结果并不可怕。创建了引用循环之后程序立刻就结束了。如果在更为复杂的程序中并在循环里分配了很多内存并占有很长时间,这个程序会使用多于它所需要的内存,并有可能压垮系统并造成没有内存可供使用。
|
||||
|
||||
创建引用循环并不容易,但也不是不可能。如果你有包含 `Rc<T>` 的 `RefCell<T>` 值或类似的嵌套结合了内部可变性和引用计数的类型,请务必小心确保你没有形成一个引用循环;你无法指望 Rust 帮你捕获它们。创建引用循环是一个程序上的逻辑 bug,你应该使用自动化测试、代码评审和其他软件开发最佳实践来使其最小化。
|
||||
|
||||
@ -55,7 +56,9 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
||||
|
||||
### 避免引用循环:将 `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<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 时被打断。
|
||||
|
||||
@ -111,7 +114,7 @@ Rust 的内存安全性保证使其难以意外地制造永远也不会被清理
|
||||
|
||||
<span class="caption">示例 15-28:一个 `leaf` 节点,其拥有指向其父节点 `branch` 的 `Weak` 引用</span>
|
||||
|
||||
创建 `leaf` 节点类似于示例 15-27 中如何创建 `leaf` 节点的,除了 `parent` 字段有所不同:`leaf` 开始时没有父节点,所以我们新建了一个空的 `Weak` 引用实例。
|
||||
创建 `leaf` 节点类似于示例 15-27,除了 `parent` 字段有所不同:`leaf` 开始时没有父节点,所以我们新建了一个空的 `Weak` 引用实例。
|
||||
|
||||
此时,当尝试使用 `upgrade` 方法获取 `leaf` 的父节点引用时,会得到一个 `None` 值。如第一个 `println!` 输出所示:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user