update to ch11-02

main
KaiserY 1 week ago
parent e652086991
commit 17a41b5b4d

@ -1,7 +1,6 @@
## 用 `Result` 处理可恢复的错误
<!-- https://github.com/rust-lang/book/blob/main/src/ch09-02-recoverable-errors-with-result.md -->
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
[ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/13e27c4a35705c4bd473bd90a3d3a8f87ef9ae58/src/ch09-02-recoverable-errors-with-result.md)
大部分错误并没有严重到需要程序完全停止执行。有时函数失败的原因很容易理解并加以处理。例如,如果因为打开一个并不存在的文件而失败,此时我们可能想要创建这个文件,而不是终止进程。
@ -93,7 +92,7 @@ enum Result<T, E> {
>
> 虽然这段代码有着如示例 9-5 一样的行为,但并没有包含任何 `match` 表达式且更容易阅读。在阅读完第十三章后再回到这个例子,并查看标准库文档 `unwrap_or_else` 方法都做了什么操作。在处理错误时,还有很多这类方法可以消除大量嵌套的 `match` 表达式。
### 失败时 panic 的快捷方式`unwrap` 和 `expect`
#### 失败时 panic 的快捷方式
`match` 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。`Result<T, E>` 类型定义了很多辅助方法来处理各种更为特定的任务。`unwrap` 方法是一个快捷方式,其内部实现与我们在 Listing 9-4 中编写的 `match` 表达式相同。如果 `Result` 值是变体 `Ok``unwrap` 会返回 `Ok` 中的值。如果 `Result` 是变体 `Err``unwrap` 会为我们调用 `panic!`。这里是一个实践 `unwrap` 的例子:
@ -152,9 +151,9 @@ hello.txt should be included in this project: Os { code: 2, kind: NotFound, mess
调用这个函数的代码最终会得到一个包含用户名的 `Ok` 值,或者一个包含 `io::Error``Err` 值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个 `Err` 值,他们可能会选择 `panic!` 并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适的处理方法。
这种传播错误的模式在 Rust 是如此的常见,以至于 Rust 提供了 `?` 问号运算符来来简化这一过程。
这种传播错误的模式在 Rust 中太常见了,因此 Rust 提供了问号运算符 `?` 来简化这一过程。
### 传播错误的快捷方式:`?` 运算符
#### `?` 运算符快捷方式
示例 9-7 展示了一个 `read_username_from_file` 的实现,它实现了与示例 9-6 中的代码相同的功能,不过这个实现使用了 `?` 运算符:
@ -166,9 +165,9 @@ hello.txt should be included in this project: Os { code: 2, kind: NotFound, mess
<span class="caption">示例 9-7一个使用 `?` 运算符向调用者返回错误的函数</span>
`Result` 值之后的 `?` 被定义为与示例 9-6 中定义的处理 `Result` 值的 `match` 表达式有着几乎完全相同的工作方式。如果 `Result` 的值是 `Ok`,这个表达式将会返回 `Ok` 中的值而程序将继续执行。如果值是 `Err``Err` 将作为整个函数的返回值,就好像使用了 `return` 关键字一样,这样错误值就被传播给了调用者。
放在 `Result` 值后面的 `?`,其定义的工作方式与我们在示例 9-6 中编写的处理 `Result` 值的 `match` 表达式几乎完全相同。如果 `Result` 的值是 `Ok`,这个表达式就会返回 `Ok` 中的值,程序继续执行。如果值是 `Err``Err` 就会像使用了 `return` 关键字一样,作为整个函数的返回值提前返回,这样错误值就被传播给了调用者。
示例 9-6 中的 `match` 表达式`?` 运算符所做的有一点不同:`?` 运算符所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 运算符调用 `from` 函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。
示例 9-6 中的 `match` 表达式`?` 运算符还有一点不同:被 `?` 作用的错误值会经过 `from` 函数。这个函数定义在标准库的 `From` trait 中,用于把一种类型的值转换成另一种类型。当 `?` 运算符调用 `from` 函数时,接收到的错误类型会被转换成当前函数返回类型里定义的错误类型。当一个函数用单一错误类型来表示它所有可能的失败方式时,这会非常有用,即使函数内部的不同部分可能会因为很多不同的原因而失败。
例如,我们可以将示例 9-7 中的 `read_username_from_file` 函数修改为返回一个自定义的 `OurError` 错误类型。如果我们也定义了 `impl From<io::Error> for OurError` 来从 `io::Error` 构造一个 `OurError` 实例,那么 `read_username_from_file` 函数体中的 `?` 运算符调用会调用 `from` 并转换错误而无需在函数中增加任何额外的代码。
@ -246,7 +245,7 @@ hello.txt should be included in this project: Os { code: 2, kind: NotFound, mess
<span class="caption">示例 9-12: 修改 `main` 返回 `Result<(), E>` 允许对 `Result` 值使用 `?` 运算符</span>
`Box<dyn Error>` 类型是一个**trait 对象***trait object*这在第十八章[顾及不同类型值的 trait 对象”][trait-objects] 部分会做介绍。目前可以将 `Box<dyn Error>` 理解为 “任何类型的错误”。在返回 `Box<dyn Error>` 错误类型的 `main` 函数中`Result` 使用 `?` 是被允许的,因为它允许任何 `Err` 值提前返回。即便 `main` 函数体从来只会返回 `std::io::Error` 错误类型,通过指定 `Box<dyn Error>`,这个签名也仍是正确的,甚至当 `main` 函数体中增加更多返回其他错误类型的代码,这个函数签名依然保持正确。
`Box<dyn Error>` 类型是一个**trait 对象***trait object*),第十八章[顾及不同类型值的 trait 对象”][trait-objects]部分会介绍它。现在可以把 `Box<dyn Error>` 理解为“任何类型的错误”。在返回错误类型 `Box<dyn Error>``main` 函数中,`Result` 使用 `?` 是被允许的,因为它允许任何 `Err` 值提前返回。即便 `main` 函数体现在只会返回 `std::io::Error` 错误类型,通过指定 `Box<dyn Error>`,这个签名仍然是正确的;即使以后在 `main` 函数体中加入返回其他错误类型的代码,这个函数签名依然保持正确。
`main` 函数返回 `Result<(), E>`,如果 `main` 返回 `Ok(())` 可执行程序会以 `0` 值退出,而如果 `main` 返回 `Err` 值则会以非零值退出;成功退出的程序会返回整数 `0`,运行错误的程序会返回非 `0` 的整数。Rust 也会从二进制程序中返回与这个惯例相兼容的整数。

@ -1,7 +1,6 @@
## 要不要 `panic!`
<!-- https://github.com/rust-lang/book/blob/main/src/ch09-03-to-panic-or-not-to-panic.md -->
<!-- commit a6bede67a2ea46a2edb3cc546cb0c908cc3e5539 -->
[ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/13e27c4a35705c4bd473bd90a3d3a8f87ef9ae58/src/ch09-03-to-panic-or-not-to-panic.md)
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
@ -15,15 +14,15 @@
如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为 `panic!` 会将测试标记为失败,此时调用 `unwrap``expect` 是恰当的。
### 当我们比编译器知道更多的情况
### 当你比编译器知道更多时
当你有一些其他的逻辑来确保 `Result` 会是 `Ok` 值时,调用 `unwrap` 或者 `expect` 也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个 `Result` 值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,更好的做法是在 `expect` 的提示文本中说明你认为永远不会出现 `Err` 的原因。下面是一个示例:。这里是一个例子:
当你有某些其他逻辑来确保 `Result` 一定会是 `Ok` 值,而编译器又无法理解这套逻辑时,调用 `expect` 也是合适的。你依然拿到的是一个必须处理的 `Result` 值:一般来说,你调用的这个操作仍然有失败的可能,尽管在你的这个具体场景里,逻辑上它不可能失败。如果你通过人工检查代码,能够确保绝不会出现 `Err` 变体,那么调用 `expect` 完全可以接受,同时最好在 `expect` 的提示文本里记录下你为什么认为这里永远不会出现 `Err`。下面是一个例子:
```rust
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-08-unwrap-that-cant-fail/src/main.rs:here}}
```
我们通过解析一个硬编码的字符来创建一个 `IpAddr` 实例。可以看出 `127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `expect` 是可以接受的。然而,拥有一个硬编码的有效的字符串也不能改变 `parse` 方法的返回值类型:它仍然是一个 `Result` 值,而编译器仍然会要求我们处理这个 `Result`,好像还是有可能出现 `Err` 变体那样。这是因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就**确实**有失败的可能性,这时确实需要我们以一种更健壮的方式处理 `Result`。提及这个 IP 地址是硬编码的假设会促使我们将来把 `expect` 替换为更好的错误处理,我们应该从其它代码获取 IP 地址
我们通过解析一个硬编码的字符串来创建 `IpAddr` 实例。我们看得出来,`127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `expect` 是可以接受的。不过,即便这个字符串是硬编码且有效的,也不会改变 `parse` 方法的返回类型:它仍然返回一个 `Result` 值,编译器也仍然要求我们像对待可能出现 `Err` 变体一样去处理这个 `Result`,因为编译器还没有聪明到能看出这个字符串总是一个有效 IP 地址。如果 IP 地址字符串来自用户输入,而不是硬编码在程序里,那么它就**确实**存在失败的可能性,这时我们肯定会希望用更健壮的方式处理 `Result`。在 `expect` 中写明这个 IP 地址是硬编码的这一假设,也会提醒我们:如果将来需要从其他来源获取 IP 地址,就该把 `expect` 改成更合适的错误处理代码
### 错误处理指导原则
@ -41,7 +40,7 @@
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个并不是 `Option` 的类型,则程序期望它是**有值**的并且不是**空值**。你的代码无需处理 `Some``None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。
### 创建自定义类型进行有效性验证
### 为验证创建自定义类型
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的我们只验证它是否为正。在这种情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
@ -59,7 +58,7 @@
相反我们可以在一个专用的模块中创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全地在函数签名中使用新类型并相信它们接收到的值。示例 9-13 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例:
<span class="filename">文件名src/lib.rs</span>
<span class="filename">文件名src/guessing_game.rs</span>
```rust
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-13/src/guessing_game.rs}}
@ -67,11 +66,11 @@
<span class="caption">示例 9-13一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span>
首先,我们创建一个名为 `guessing_game` 的新模块。接着定义一个包含 `i32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方
注意,这段位于 *src/guessing_game.rs* 的代码依赖于在 *src/lib.rs* 中添加 `mod guessing_game;` 模块声明,不过这里没有把那部分展示出来。在这个新模块文件里,我们定义了一个名为 `Guess` 的结构体,它有一个名为 `value` 的字段,用来保存 `i32` 类型的数字
接着`Guess` 上实现了一个叫做 `new` 的关联函数来创建 `Guess` 的实例。`new` 定义为接收一个 `i32` 类型的参数 `value` 并返回一个 `Guess`。`new` 函数中代码的测试确保了其值是在 1 到 100 之间的。如果 `value` 没有通过测试则调用 `panic!`,这会警告调用这个函数的程序员有一个需要修改的 bug因为创建一个 `value` 超出范围的 `Guess` 将会违反 `Guess::new` 所遵循的契约。`Guess::new` 会出现 panic 的条件应该在其公有 API 文档中被提及;第十四章会涉及到在 API 文档中表明 `panic!` 可能性的相关规则。如果 `value` 通过了测试,我们新建一个 `Guess`,其字段 `value` 将被设置为参数 `value` 的值,接着返回这个 `Guess`
接着,我们在 `Guess` 上实现了一个名为 `new` 的关联函数,用来创建 `Guess` 的实例。`new` 接收一个名为 `value`、类型为 `i32` 的参数,并返回一个 `Guess`。`new` 函数体中的代码会检查 `value` 是否位于 1 到 100 之间。如果 `value` 没有通过这个测试,我们就调用 `panic!`,提醒调用这段代码的程序员:他们的代码里有一个需要修复的 bug因为创建一个 `value` 超出这个范围的 `Guess`,会违反 `Guess::new` 所依赖的契约。`Guess::new` 在什么条件下可能 panic应该写进它面向外部的 API 文档中;第十四章会讲到如何在 API 文档里表明 `panic!` 的可能性。如果 `value` 通过了测试,我们就创建一个新的 `Guess`,把它的 `value` 字段设置为参数 `value` 的值,然后返回这个 `Guess`
接着,我们实现了一个借用了 `self` 的方法 `value`,它没有任何其他参数并返回一个 `i32`。这类方法有时被称为 *getter*,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。私有的字段 `value` 是很重要的,这样使用 `Guess` 结构体的代码将不允许直接设置 `value` 的值:调用者**必须**使用 `Guess::new` 方法来创建一个 `Guess` 的实例,这就确保了不会存在一个 `value` 没有通过 `Guess::new` 函数的条件检查的 `Guess`。
接着,我们实现了一个名为 `value` 的方法:它借用 `self`,没有其他参数,并返回一个 `i32`。这种方法有时被称为 *getter*,因为它的目的就是从字段中取出一些数据并返回它。这个公有方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。`value` 保持私有非常重要,因为这样使用 `Guess` 结构体的代码就不能直接设置 `value``guessing_game` 模块之外的代码**必须**通过 `Guess::new` 来创建 `Guess` 实例,从而保证 `Guess` 不可能拥有一个未经 `Guess::new` 条件检查的 `value`。
于是,一个接收或返回 1 到 100 之间数字的函数就可以声明为接收(或返回) `Guess` 的实例,而不是 `i32`,同时其函数体中也无需进行任何额外的检查。

@ -1,17 +1,16 @@
# 泛型、Trait 和生命周期
<!-- https://github.com/rust-lang/book/blob/main/src/ch10-00-generics.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch10-00-generics.md](https://github.com/rust-lang/book/blob/3986f214fd82427b4401adf4d7dc0911c917e1e8/src/ch10-00-generics.md)
一个编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是 **泛型***generics*):具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如它们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道它们在这里实际上代表什么
种编程语言都有一些工具,用来高效处理概念上的重复。在 Rust 中,这样的工具之一就是**泛型***generics*):它是具体类型或其他属性的抽象占位符。我们可以描述泛型的行为,或者它们与其他泛型之间的关系,而不需要在编译和运行代码时就提前知道它们具体会被什么替换
函数可以获取一些不同于 `i32``String` 这样具体类型的泛型参数,就像一个获取未知类型值的函数可以对多种具体类型的值运行同一段代码一样。事实上我们已经使用过第六章的 `Option<T>`,第八章的 `Vec<T>``HashMap<K, V>`,以及第九章的 `Result<T, E>` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法!
函数可以像接收未知值那样,接收某种泛型类型的参数,而不是像 `i32``String` 这样的具体类型,从而让同一段代码可以作用于多种具体值。事实上,我们已经在第六章见过 `Option<T>`,在第八章见过 `Vec<T>``HashMap<K, V>`,在第九章见过 `Result<T, E>`。本章将继续探索如何使用泛型来定义我们自己的类型、函数和方法!
首先,我们将回顾一下提取函数以减少代码重复的机制。接下来,我们将使用相同的技术,从两个仅参数类型不同的函数中创建一个泛型函数。我们也会讲到结构体和枚举定义中的泛型。
首先,我们会回顾如何通过提取函数来减少代码重复。然后,我们会使用同样的技巧,把两个只在参数类型上不同的函数变成一个泛型函数。我们也会解释如何在结构体和枚举定义中使用泛型类型。
之后,我们讨论如何使用 **trait** 定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为只接受拥有特定行为的类型,而不是任意类型。
然后,你会学到如何使用 **trait** 以泛型的方式定义行为。你可以把 trait 和泛型类型组合起来,把某个泛型类型限制为只接受具有特定行为的类型,而不是任意类型。
最后介绍 **生命周期***lifetimes*一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在更多场景下借用值的同时仍然使编译器能够检查这些引用的有效性而不用借助我们的帮助
最后,我们会讨论 **生命周期***lifetimes*):它是一类向编译器提供“引用之间如何相互关联”信息的泛型。生命周期让我们能够向编译器提供足够的信息,使它在更多场景下也能确认引用是有效的
## 提取函数来减少重复
@ -27,9 +26,9 @@
<span class="caption">示例 10-1在一个数字列表中寻找最大值的函数</span>
这段代码获取一个整型列表,存放在变量 `number_list` 中。它将列表的第一个数字的引用放入了变量 `largest` 中。接着遍历了列表中的所有数字,如果当前值大于 `largest` 中储存的值,将 `largest` 替换为这个值。如果当前值小于或者等于目前为止的最大值,变量保持不变,并且代码移动到列表中的下一个数字。当列表中所有值都被考虑到之后,`largest` 将会指向最大值,在这里也就是 100。
我们把一个整数列表存进变量 `number_list`,再把列表中第一个数字的引用放进名为 `largest` 的变量。然后遍历列表中的所有数字:如果当前数字比 `largest` 中存储的数字更大,就更新这个变量中的引用。反之,如果当前数字小于或等于目前为止见到的最大值,这个变量就保持不变,代码继续处理列表中的下一个数字。当列表中所有数字都比较完后,`largest` 就会指向最大值;在这个例子里,它是 100。
我们的任务是在两个不同的数字列表中寻找最大值。为此我们可以选择重复示例 10-1 中的代码在程序的两个不同位置使用相同的逻辑,如示例 10-2 所示:
现在我们又接到一个任务:要在两个不同的数字列表中找出最大值。为此,我们可以选择把示例 10-1 中的代码复制一份,在程序的两个不同位置使用同样的逻辑,如示例 10-2 所示:
<span class="filename">文件名src/main.rs</span>
@ -39,11 +38,11 @@
<span class="caption">示例 10-2寻找**两个**数组最大值的代码</span>
虽然代码能够执行,但是重复的代码是冗余且容易出错的,更新逻辑时我们不得不记住需要修改多处地方的代码
虽然这段代码能工作,但复制代码既繁琐又容易出错。而且当我们想修改逻辑时,还得记得去更新多处地方
为了消除重复,我们要创建一层抽象,定义一个处理任意整型列表作为参数的函数。这个方案使得代码更简洁,并且表现了寻找任意列表中最大值这一概念。
为了消除这种重复,我们将通过定义一个以任意整数列表为参数的函数来创建抽象。这个方案让代码更清晰,也能让我们以更抽象的方式表达“找出一个列表中最大数字”这个概念。
在示例 10-3 的程序中将寻找最大值的代码提取到了一个叫做 `largest` 的函数中。接着我们调用该函数来寻找示例 10-2 中两个列表中的最大值。之后也可以将该函数用于任何可能的 `i32` 值的列表
在示例 10-3 中,我们把寻找最大值的代码提取到了一个名为 `largest` 的函数中。然后调用这个函数,来找出示例 10-2 中两个列表里的最大值。将来如果我们还有别的 `i32` 值列表,也同样可以复用这个函数
<span class="filename">文件名src/main.rs</span>
@ -53,14 +52,14 @@
<span class="caption">示例 10-3抽象后的寻找两个数字列表最大值的代码</span>
`largest` 函数有一个参数 `list`,它代表会传递给函数的任何具体的 `i32`值的 slice。函数定义中的 `list` 代表任何 `&[i32]`。当调用 `largest` 函数时,其代码实际上运行于我们传递的特定值上。
`largest` 函数有一个名为 `list` 的参数,它表示任何传给该函数的 `i32` 值切片。因此,当我们调用这个函数时,代码会运行在我们传入的具体值上。
总的来说,从示例 10-2 到示例 10-3 中代码的修改经历了如下几步
总的来说,我们把代码从示例 10-2 改成示例 10-3 时,经历了下面这些步骤
1. 找出重复代码。
2. 将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
3. 将重复代码的两个实例,改为调用函数。
接下来我们会使用相同的步骤通过泛型来减少重复。与函数体可以处理任意的 `list` 而不是具体的值一样,泛型也允许代码处理任意类型。
接下来,我们会用同样的步骤借助泛型来减少重复。就像函数体可以处理抽象的 `list`、而不是特定的值一样,泛型也允许代码处理抽象类型。
如果我们有两个函数,一个寻找一个 `i32` 值的 slice 中的最大项而另一个寻找 `char` 值的 slice 中的最大项该怎么办?该如何消除重复呢?让我们拭目以待!
例如,假设我们有两个函数:一个用来找出 `i32` 切片中的最大项,另一个用来找出 `char` 切片中的最大项。我们该如何消除这种重复呢?继续往下看。

@ -1,15 +1,14 @@
## 泛型数据类型
<!-- https://github.com/rust-lang/book/blob/main/src/ch10-01-syntax.md -->
<!-- commit 6c22ad745951671f9ee2689936881e62ef2a8069 -->
[ch10-01-syntax.md](https://github.com/rust-lang/book/blob/3986f214fd82427b4401adf4d7dc0911c917e1e8/src/ch10-01-syntax.md)
我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。
我们使用泛型来为函数签名或结构体之类的项创建定义,这样它们就可以配合多种不同的具体数据类型使用。先来看看如何使用泛型定义函数、结构体、枚举和方法。然后再讨论泛型会如何影响代码性能。
### 在函数定义中使用泛型
当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。接着我们使用泛型将其合并为一个函数。
回到 `largest` 函数。示例 10-4 展示了两个都用来寻找切片中最大值的函数。接着,我们会把它们合并成一个使用泛型的函数。
<span class="filename">文件名src/main.rs</span>
@ -21,15 +20,15 @@
`largest_i32` 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 `i32`。`largest_char` 函数寻找 slice 中最大的 `char`。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。
为了参数化这个新函数中的这些类型,我们需要为类型参数命名,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T`因为传统上来说Rust 的类型参数名字都比较短通常仅为一个字母同时Rust 类型名的命名规范是首字母大写驼峰式命名法UpperCamelCase。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选
为了给这个新函数中的类型做参数化,我们需要给类型参数命名,就像给函数的值参数命名一样。任何标识符都可以作为类型参数名。但这里我们使用 `T`因为按照惯例Rust 中的类型参数名都很短,通常只有一个字母,而 Rust 类型名的命名约定是 UpperCamelCase。`T` 是 _type_ 的缩写,也是大多数 Rust 程序员的默认选择
如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>`,像这样:
如果你要在函数体中使用某个参数,就必须先在函数签名中声明它的名字,让编译器知道这个名字表示什么。同理,当你在函数签名中使用类型参数名时,也必须先声明它。为了定义泛型版的 `largest` 函数,我们把类型名声明放在函数名和参数列表之间的尖括号 `<>`,像这样:
```rust,ignore
fn largest<T>(list: &[T]) -> &T {
```
可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数会返回一个与 `T` 相同类型的引用。
我们可以把这个定义读作:“函数 `largest` 对某个类型 `T` 是泛型的。”这个函数有一个名为 `list` 的参数,它是由 `T` 类型值组成的切片。`largest` 函数会返回一个指向同样类型 `T`的引用。
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译。
@ -47,11 +46,11 @@ fn largest<T>(list: &[T]) -> &T {
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}}
```
帮助说明中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait。不过简单来说这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型。因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。依照帮助说明中的建议,我们限制 `T` 只对实现了 `PartialOrd` 的类型有效后代码就可以编译了,因为标准库`i32``char` 实现了 `PartialOrd`
帮助信息里提到了 `std::cmp::PartialOrd`,这是一个 trait我们会在下一节讨论 trait。现在先知道这个错误说明 `largest` 的函数体并不能适用于 `T` 所有可能的类型。因为我们想在函数体中比较 `T` 类型的值,所以只能使用那些值可以排序的类型。为了支持比较,标准库提供了 `std::cmp::PartialOrd` trait你可以为类型实现它更多内容见附录 C。要修复示例 10-5我们可以按照帮助信息的建议`T` 限制为只接受实现了 `PartialOrd` 的类型。这样代码就能编译,因为标准库已经`i32``char` 实现了 `PartialOrd`
### 结构体定义中的泛型
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 定义了一个可以存放任何类型的 `x``y` 坐标值的结构体 `Point`
我们也可以使用 `<>` 语法来定义结构体,让一个或多个字段使用泛型类型参数。示例 10-6 定义了一个 `Point<T>` 结构体,用来保存任意类型的 `x``y` 坐标值
<span class="filename">文件名src/main.rs</span>
@ -89,7 +88,7 @@ fn largest<T>(list: &[T]) -> &T {
<span class="caption">示例 10-8使用两个泛型的 `Point`,这样 `x``y` 可能是不同类型</span>
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你发现代码中需要很多泛型时,这可能表明你的代码需要重构分解成更小的结构
现在,示例里的所有这些 `Point` 实例都合法了!你可以在定义中使用任意多个泛型类型参数,但如果超过几个,代码就会变得难以阅读。如果你发现自己在代码里需要很多泛型类型,那可能意味着这段代码应该被重构成更小的部分
### 枚举定义中的泛型
@ -119,7 +118,7 @@ enum Result<T, E> {
### 方法定义中的泛型
在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
我们可以像第五章那样为结构体和枚举实现方法,并在这些方法定义中使用泛型。示例 10-9 展示了示例 10-6 中定义的 `Point<T>` 结构体,以及在其上实现的一个名为 `x` 的方法。
<span class="filename">文件名src/main.rs</span>
@ -161,9 +160,9 @@ enum Result<T, E> {
### 泛型代码的性能
你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得慢。
你可能会好奇,使用泛型类型参数是否会带来运行时开销。好消息是:使用泛型不会让程序比使用具体类型运行得更慢。
Rust 通过在编译时进行泛型代码的**单态化***monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程
Rust 通过在编译时对泛型代码进行**单态化***monomorphization*)来实现这一点。单态化就是把泛型代码转换成具体代码的过程,方法是用编译时实际用到的具体类型去填充泛型代码
在这个过程中,编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。

@ -1,7 +1,6 @@
## Trait定义共同行为
<!-- https://github.com/rust-lang/book/blob/main/src/ch10-02-traits.md -->
<!-- commit e5c8a0c99d5d3d830ad35e67f203a3378b4de16f -->
[ch10-02-traits.md](https://github.com/rust-lang/book/blob/3986f214fd82427b4401adf4d7dc0911c917e1e8/src/ch10-02-traits.md)
*trait* 定义了某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共同行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
@ -55,7 +54,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
但是不能为外部类型实现外部 trait。例如不能在 `aggregator` crate 中为 `Vec<T>` 实现 `Display` trait。这是因为 `Display``Vec<T>` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为**相干性***coherence*)的程序属性的一部分,或者更具体的说是 **孤儿规则***orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你的代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait而 Rust 将无从得知应该使用哪一个实现。
### 默认实现
### 使用默认实现
有时为 trait 中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。
@ -103,7 +102,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
请注意,无法从一个方法的重写实现中调用与其同名的默认实现。
### trait 作为参数
### 使用 trait 作为参数
知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。示例 10-13 中为 `NewsArticle``SocialPost` 类型实现了 `Summary` trait用其来定义了一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样:
@ -131,7 +130,7 @@ pub fn notify<T: Summary>(item: &T) {
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
```
这适用于 `item1``item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?但如果我们希望强制两个参数必须具有相同类型,则必须使用 trait bound如下所示
这适用于 `item1``item2` 可以是不同类型的情况,只要它们都实现了 `Summary`。不过,如果我们希望强制两个参数必须具有相同类型,就必须使用 trait bound如下所示
```rust,ignore
pub fn notify<T: Summary>(item1: &T, item2: &T) {
@ -139,7 +138,7 @@ pub fn notify<T: Summary>(item1: &T, item2: &T) {
泛型 `T` 被指定为 `item1``item2` 的参数限制,如此传递给参数 `item1``item2` 值的具体类型必须一致。
#### 通过 `+` 指定多个 trait bound
#### 通过 `+` 语法指定多个 trait bound
我们也可以指定多个 trait bound。假设我们希望 `notify``item` 上既能使用格式化显示,又能使用 `summarize` 方法:在 `notify` 的定义中,指定 `item` 必须同时实现 `Display``Summary` 两个 trait。这可以通过 `+` 语法实现:
@ -189,7 +188,7 @@ fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-06-impl-trait-returns-one-type/src/lib.rs:here}}
```
这里尝试返回 `NewsArticle``SocialPost` 是不被允许的,原因在于编译器中 `impl Trait` 语法的实现限制。第十八章的 [“顾及不同类型值的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分会介绍如何编写这样一个函数。
这里尝试返回 `NewsArticle``SocialPost` 是不被允许的,原因在于编译器中 `impl Trait` 语法的实现限制。第十八章的[“顾及不同类型值的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types]部分会介绍如何编写这样一个函数。
### 使用 trait bound 有条件地实现方法

@ -1,7 +1,6 @@
## 生命周期确保引用有效
<!-- https://github.com/rust-lang/book/blob/main/src/ch10-03-lifetime-syntax.md -->
<!-- commit f6bd152fa77964b706b5cb9e3cc90915354e0cda -->
[ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/3986f214fd82427b4401adf4d7dc0911c917e1e8/src/ch10-03-lifetime-syntax.md)
生命周期是另一类我们已经使用过的泛型。不同于确保类型有期望的行为,生命周期用于保证引用在我们需要的整个期间内都是有效的。
@ -9,7 +8,7 @@
生命周期注解甚至不是一个大部分语言都有的概念,所以这可能感觉起来有些陌生。虽然本章不可能涉及到它全部的内容,我们会讲到一些通常你可能会遇到的生命周期语法以便你熟悉这个概念。
### 生命周期避免了悬垂引用
### 悬垂引用
生命周期的主要目标是避免**悬垂引用***dangling references*),后者会导致程序引用了非预期引用的数据。考虑一下示例 10-16 中的程序,它有一个外部作用域和一个内部作用域。
@ -103,11 +102,11 @@ Rust 编译器有一个**借用检查器***borrow checker*),它比较作
单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。让我们在 `longest` 函数的上下文中理解生命周期注解如何相互联系。
### 函数签名中的生命周期注解
### 函数签名中
为了在函数签名中使用生命周期注解,需要在函数名和参数列表间的尖括号中声明泛型生命周期(*lifetime*)参数,就像泛型类型(*type*)参数一样。
我们希望函数签名表达如下限制:也就是这两个参数和返回的引用存活的一样久。(两个)参数和返回的引用的生命周期是相关的。就像示例 10-21 中在每个引用中都加上了 `'a` 那样
我们希望这个签名表达如下约束:返回的引用只要两个参数都有效,就会一直有效。这就是参数生命周期和返回值生命周期之间的关系。我们将这个生命周期命名为 `'a`,然后像示例 10-21 那样将它加到每个引用上
<span class="filename">文件名src/main.rs</span>
@ -161,7 +160,7 @@ Rust 编译器有一个**借用检查器***borrow checker*),它比较作
请尝试更多采用不同的值和不同生命周期的引用作为 `longest` 函数的参数和返回值的实验。并在开始编译前猜想你的实验能否通过借用检查器,接着编译一下看看你的理解是否正确!
### 深入理解生命周期
### 关系
指定生命周期参数的正确方式依赖函数实现的具体功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
@ -191,7 +190,7 @@ Rust 编译器有一个**借用检查器***borrow checker*),它比较作
综上生命周期语法是用于将函数的多个参数与其返回值的生命周期进行关联的。一旦它们形成了某种关联Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。
### 结构体定义中的生命周期注解
### 结构体定义中
目前为止,我们定义的结构体全都包含拥有所有权的类型。也可以定义包含引用的结构体,不过这需要为结构体定义中的每一个引用添加生命周期注解。示例 10-24 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt`
@ -279,7 +278,7 @@ fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
因为第三条规则真正能够适用的就只有方法签名,现在就让我们看看那种情况中的生命周期,并看看为什么这条规则意味着我们经常不需要在方法签名中标注生命周期。
### 方法定义中的生命周期注解
### 方法定义中
当为带有生命周期的结构体实现方法时,其语法依然类似示例 10-11 中展示的泛型类型参数的语法。我们在哪里声明和使用生命周期参数,取决于它们是与结构体字段相关还是与方法参数和返回值相关。
@ -315,7 +314,7 @@ let s: &'static str = "I have a static lifetime.";
你可能在错误信息的帮助文本中见过使用 `'static` 生命周期的建议,不过将引用指定为 `'static` 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效,以及你是否希望它存在得这么久。大部分情况中,推荐 `'static` 生命周期的错误信息都是尝试创建一个悬垂引用或者可用的生命周期不匹配的结果。在这种情况下的解决方案是修复这些问题而不是指定一个 `'static` 的生命周期。
### 结合泛型类型参数、trait bounds 和生命周期
### 泛型类型参数、trait bounds 和生命周期
让我们简要的看一下在同一函数中指定泛型类型参数、trait bounds 和生命周期的语法!

@ -1,13 +1,12 @@
# 编写自动化测试
<!-- https://github.com/rust-lang/book/blob/main/src/ch11-00-testing.md -->
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
[ch11-00-testing.md](https://github.com/rust-lang/book/blob/99071589c5358114de6324d9aa2643caeee305bd/src/ch11-00-testing.md)
Edsger W. Dijkstra 在其 1972 年的文章《谦卑的程序员》“The Humble Programmer”中说到 “软件测试是证明 bug 存在的有效方法,而证明其不存在时则显得令人绝望的不足。”“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”)这并不意味着我们不应尽可能地测试软件
Edsger W. Dijkstra 在其 1972 年的文章《谦卑的程序员》“The Humble Programmer”中说:“软件测试是证明 bug 存在的有效方法,而要证明 bug 不存在,它就显得远远不够。”“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”)这并不意味着我们不应尽可能地测试!
程序的正确性意味着代码如我们期望的那样运行。Rust 是一个相当注重正确性的编程语言不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫不过类型系统不可能捕获所有问题。为此Rust 包含了编写自动化软件测试的功能支持。
程序的正确性指的是代码在多大程度上实现了我们的预期。Rust 在设计上高度重视程序的正确性不过正确性是一个复杂且不易证明的主题。Rust 的类型系统承担了这方面很大一部分工作但类型系统不可能捕获所有问题。为此Rust 提供了编写自动化软件测试的支持。
假设我们可以编写一个叫做 `add_two` 的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 `String` 或无效的引用给这个函数。Rust 所**不能**检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这正是测试的用武之地
假设我们编写了一个叫做 `add_two` 的函数,它会把传给它的数字加上 2。这个函数的签名接受一个整数参数并返回一个整数结果。当我们实现并编译这个函数时Rust 会进行目前你已经见过的所有类型检查和借用检查,以确保例如我们不会向这个函数传递一个 `String` 值或无效引用。但 Rust **无法**检查这个函数是否真的精确实现了我们的意图,也就是返回“参数加 2”而不是比如“参数加 10”或“参数减 50”这正是测试发挥作用的地方
我们可以编写测试断言,比如说,当传递 `3``add_two` 函数时,返回值是 `5`。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。

@ -1,7 +1,6 @@
## 如何编写测试
<!-- https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md -->
<!-- commit 02e053cdbbb3bf9edd9ad32ed49eb533404350a9 -->
[ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/99071589c5358114de6324d9aa2643caeee305bd/src/ch11-01-writing-tests.md)
Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:
@ -11,7 +10,7 @@ Rust 中的测试函数是用来验证非测试代码是否按照期望的方式
让我们看看 Rust 提供的专门用来编写测试的功能:`test` 属性、一些宏和 `should_panic` 属性。
### 测试函数剖析
### 精心组织测试函数
作为最简单例子Rust 中的测试就是一个带有 `test` 属性注解的函数。属性attribute是关于 Rust 代码片段的元数据;第五章中结构体中用到的 `derive` 属性就是一个例子。为了将一个函数变成测试函数,需要在 `fn` 行之前加上 `#[test]`。当使用 `cargo test` 命令运行测试时Rust 会构建一个测试执行程序用来调用被标注的函数,并报告每一个测试是通过还是失败。
@ -53,7 +52,7 @@ $ cd adder
Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下一行显示了生成的测试函数的名称,`tests::it_works`,以及测试的运行结果,`ok`。接着可以看到全体测试运行结果的摘要:`test result: ok.` 意味着所有测试都通过了。`1 passed; 0 failed` 表示通过或失败的测试数量。
可以将一个测试标记为忽略以便在特定情况下它就不会运行;本章之后的[“除非特别指定否则忽略某些测试”][ignoring]部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`
可以将一个测试标记为忽略以便在特定情况下它就不会运行;本章之后的[“除非特别指定否则忽略测试”][ignoring]部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`
`0 measured` 统计是针对性能测试的。性能测试benchmark tests在编写本书时仍只能用于 Rust 开发版nightly Rust。请查看 [性能测试的文档][bench] 了解更多。
@ -322,7 +321,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
[concatenation-with-the--operator-or-the-format-macro]:
ch08-02-strings.html#使用--运算符或-format-宏拼接字符串
[bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html
[ignoring]: ch11-02-running-tests.html#除非特别指定否则忽略某些测试
[ignoring]: ch11-02-running-tests.html#除非特别指定否则忽略测试
[subset]: ch11-02-running-tests.html#通过名称运行部分测试
[controlling-how-tests-are-run]:
ch11-02-running-tests.html#控制测试如何运行

@ -1,14 +1,12 @@
## 控制测试如何运行
<!-- https://github.com/rust-lang/book/blob/main/src/ch11-02-running-tests.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/99071589c5358114de6324d9aa2643caeee305bd/src/ch11-02-running-tests.md)
就像 `cargo run` 会编译代码并运行生成的二进制文件一样,`cargo test` 在测试模式下编译代码并运行生成的测试二进制文件。`cargo test` 产生的二进制文件的默认行为是并发运行所有的测试,并截获测试运行过程中产生的输出,阻止它们被显示出来,使得阅读测试结果相关的内容变得更容易。不过可以指定命令行参数来改变 `cargo test` 的默认行为。
可以将一部分命令行参数传递给 `cargo test`,而将另外一部分传递给生成的测试二进制文件。为了分隔这两种参数,需要先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help`提示 `cargo test` 的有关参数,而运行 `cargo test -- --help` 可以提示在分隔符之后使用的有关参数。有关这些选项的说明,请参阅 [the rustc book][rustc] 的 [“Tests” 一节][tests]。
可以将一部分命令行参数传递给 `cargo test`,而将另外一部分传递给生成的测试二进制文件。为了分隔这两种参数,需要先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help`显示可用于 `cargo test` 的选项,而运行 `cargo test -- --help` 会显示可用于分隔符之后的选项。有关这些选项的说明,也可以参阅 [《rustc 手册》中的 “Tests” 一节][tests]。
[tests]: https://doc.rust-lang.org/rustc/tests/index.html
[rustc]: https://doc.rust-lang.org/rustc/index.html
### 并行或顺序运行测试
@ -100,7 +98,7 @@ $ cargo test -- --show-output
这运行了所有名字中带有 `add` 的测试,也过滤掉了名为 `one_hundred` 的测试。同时注意测试所在的模块也是测试名称的一部分,所以可以通过过滤模块名来运行一个模块中的所有测试。
### 除非特别指定否则忽略某些测试
### 除非特别指定否则忽略测试
有时一些特定的测试执行起来是非常耗费时间的,所以在大多数运行 `cargo test` 的时候希望能排除它们。虽然可以通过参数列举出所有希望运行的测试来做到,也可以使用 `ignore` 属性来标记耗时的测试并排除它们,如下所示:

@ -262,8 +262,8 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,这些工作在新代码中非常容易实现:那就是编写测试!
[ch13]: ch13-00-functional-features.html
[ch9-custom-types]: ch09-03-to-panic-or-not-to-panic.html#创建自定义类型进行有效性验证
[ch9-custom-types]: ch09-03-to-panic-or-not-to-panic.html#为验证创建自定义类型
[ch9-error-guidelines]: ch09-03-to-panic-or-not-to-panic.html#错误处理指导原则
[ch9-result]: ch09-02-recoverable-errors-with-result.html
[ch18]: ch18-00-oop.html
[ch9-question-mark]: ch09-02-recoverable-errors-with-result.html#传播错误的快捷方式-运算符
[ch9-question-mark]: ch09-02-recoverable-errors-with-result.html#-运算符快捷方式

@ -163,7 +163,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
[validating-references-with-lifetimes]:
ch10-03-lifetime-syntax.html#生命周期确保引用有效
[ch11-anatomy]: ch11-01-writing-tests.html#测试函数剖析
[ch11-anatomy]: ch11-01-writing-tests.html#精心组织测试函数
[ch10-lifetimes]: ch10-03-lifetime-syntax.html
[ch3-iter]: ch03-05-control-flow.html#使用-for-遍历集合
[ch13-iterators]: ch13-02-iterators.html

@ -107,4 +107,4 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13
不过这两种实现真的完全等价吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。
[impl-trait]: ch10-02-traits.html#trait-作为参数
[impl-trait]: ch10-02-traits.html#使用-trait-作为参数

@ -221,7 +221,7 @@ enum Either<A, B> {
[futures-crate]: https://crates.io/crates/futures
[tokio]: https://tokio.rs
[impl-trait]: ch10-02-traits.html#trait-作为参数
[impl-trait]: ch10-02-traits.html#使用-trait-作为参数
[iterators-lazy]: ch13-02-iterators.html
[thread-spawn]: ch16-01-threads.html#creating-a-new-thread-with-spawn
[cli-args]: ch12-01-accepting-command-line-arguments.html

@ -138,7 +138,7 @@
这里调用 `Option``as_ref` 方法是因为需要 `Option` 中值的引用而不是获取其所有权。因为 `state` 是一个 `Option<Box<dyn State>>`,调用 `as_ref` 会返回一个 `Option<&Box<dyn State>>`。如果不调用 `as_ref`,将会得到一个错误,因为不能将 `state` 移动出借用的 `&self` 函数参数。
接着调用 `unwrap` 方法,这里我们知道它永远也不会 panic因为 `Post` 的所有方法都确保在它们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章 [“当我们比编译器知道更多的情况”][more-info-than-rustc] 部分讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况之一。
接着调用 `unwrap` 方法,这里我们知道它永远也不会 panic因为 `Post` 的所有方法都确保在它们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章 [“当你比编译器知道更多时”][more-info-than-rustc] 部分讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况之一。
接着我们就有了一个 `&Box<dyn State>`,当调用其 `content` 时,解引用强制转换会作用于 `&``Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 18-18 所示:
@ -246,5 +246,5 @@
接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。我们在全书中已多次简要提及它们,但尚未充分领略它们的全部威力。让我们开始探索吧!
[more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#当我们比编译器知道更多的情况
[more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#当你比编译器知道更多时
[macros]: ch20-05-macros.html#宏

Loading…
Cancel
Save