Fixing link id

This commit is contained in:
Peng Hailin, 2023-04-09 08:53:16 +08:00
parent 465126d27f
commit c877706ed2
11 changed files with 65 additions and 63 deletions

View File

@ -556,7 +556,7 @@ fn main () {
- 糟糕状态是某些不期望的东西,他们与偶发的东西相反,比如用户输入的错误格式数据;
- 在此处之后的代码,需要依赖于不处在这种糟糕状态,而不是在接下来的每一步都检查这个问题;
- 没有以自己所使用的类型,来编码该信息的好办法。在第 17 章的 [“将状态与行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#) 小节,就会贯穿一个这里所意指的示例。
- 没有以自己所使用的类型,来编码该信息的好办法。在第 17 章的 [“将状态与行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#将状态与行为当作类型编码) 小节,就会贯穿一个这里所意指的示例。
在有人调用到咱们的代码,并传入了无意义的值时,在可以的情况下,最好返回一个错误,这样库用户就可以确定在那样的情况下,他们打算做什么。然而在继续执行下去会不安全或有危害的情形中,那么最佳选择就会时调用 `panic!`,并警醒使用到咱们库的人他们代码中的错误,这样在他们开发过程中就可以修好那个代码错误。与此类似,在调用不在掌控中的外部代码,且该外部代码返回了无法修复的无效状态时,那么 `panic!` 通常就是恰当选择。

View File

@ -793,12 +793,12 @@ fn returns_summarizable(switch: bool) -> impl Summary {
}
```
这里不允许既返回 `NewsArticle` 又返回 `Tweet`,是由于有关这个 `impl Trait` 语法,在编译器中实现方式的限制。在第 17 章的 [运用允许不同类型值的特质对象](Ch17_Object_Oriented_Programming_Features_of_Rust.md#using-trait-objects-that-allow-for-values-of-different-types) 小节,就会讲到怎样编写有着这种行为的函数。
这里不允许既返回 `NewsArticle` 又返回 `Tweet`,是由于有关这个 `impl Trait` 语法,在编译器中实现方式的限制。在第 17 章的 [运用允许不同类型值的特质对象](Ch17_Object_Oriented_Programming_Features_of_Rust.md#使用允许不同类型值的特质对象) 小节,就会讲到怎样编写有着这种行为的函数。
### 运用特质边界来有条件地实现方法
经由运用有着一个使用泛型参数的 `impl` 代码块的特质边界就可以根据实现了指定特质的类型而实现不同方法by using a trait bound with an `impl` block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits。比如下面清单 10-15 中的类型 `Pair<T>`,就一直会将那个 `new` 函数,实现为返回 `Pair<T>` 的一个新实例(回顾第 5 章的 [定义方法](Ch05_Using_Structs_to_Structure_Related_Data.md#defining-methods) 小节就知道,`Self` 就是那个 `impl` 代码块的类型别名,在这个示例中即为 `Pair<T>`)。但在接下来的 `impl` 代码块中,在 `Pair<T>` 的内部类型 `T` 实现了启用比较的 `PartialOrd` 特质,*与* 启用打印的 `Display` 特质时,那么 `Pair<T>` 就只会实现 `cmp_display` 方法。
经由运用有着一个使用泛型参数的 `impl` 代码块的特质边界就可以根据实现了指定特质的类型而实现不同方法by using a trait bound with an `impl` block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits。比如下面清单 10-15 中的类型 `Pair<T>`,就一直会将那个 `new` 函数,实现为返回 `Pair<T>` 的一个新实例(回顾第 5 章的 [定义方法](Ch05_Using_Structs_to_Structure_Related_Data.md#方法的定义) 小节就知道,`Self` 就是那个 `impl` 代码块的类型别名,在这个示例中即为 `Pair<T>`)。但在接下来的 `impl` 代码块中,在 `Pair<T>` 的内部类型 `T` 实现了启用比较的 `PartialOrd` 特质,*与* 启用打印的 `Display` 特质时,那么 `Pair<T>` 就只会实现 `cmp_display` 方法。
```rust
@ -856,7 +856,7 @@ let s = 3.to_string();
生命周期是另一种前面已经用到的泛型。与确保某种类型有着期望行为的特质不同生命周期确保的是引用在需要他们有效期间保持有效lifetimes ensure that references are valid as long as we need them to be
在第 4 章中的 [引用与借用](Ch04_Understanding_Ownership.md#references-and-borrowing) 小节,未曾讨论到的一个细节,就是在 Rust 中的全部引用都有着 *生命周期lifetime*即引用有效的作用范围。多数时候声明周期都是隐式的且是被推导出来的这正与多数时候类型是被推导出来的一样。在可能有多个类型时仅务必对类型加以注解即可。与这种注解类型的方式类似在引用的生命周期与少数几种不同方式相关时就必须对生命周期加以注解。为确保在运行时用到的具体类型显著有效Rust 就要求使用泛型生命周期参数对这些关系加以注解in a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid
在第 4 章中的 [引用与借用](Ch04_Understanding_Ownership.md#引用与借用references-and-borrowing) 小节,未曾讨论到的一个细节,就是在 Rust 中的全部引用都有着 *生命周期lifetime*即引用有效的作用范围。多数时候声明周期都是隐式的且是被推导出来的这正与多数时候类型是被推导出来的一样。在可能有多个类型时仅务必对类型加以注解即可。与这种注解类型的方式类似在引用的生命周期与少数几种不同方式相关时就必须对生命周期加以注解。为确保在运行时用到的具体类型显著有效Rust 就要求使用泛型生命周期参数对这些关系加以注解in a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid
绝大多数别的编程语言,甚至都没有声明周期注解这个概念,那么这就会感觉陌生了。尽管本章不会涵盖生命周期的全部,这里仍会对可能遇到的生命周期语法的一些常见方式进行讨论,如此就会对此概念感到不那么违和。
@ -976,7 +976,7 @@ fn main() {
*清单 10-19调用 `longest` 函数来找出两个字符串切片中较长的那个的 `main` 函数*
留意到由于这里并不打算这个 `longest` 函数取得其参数的所有权,因此这里是要该函数取两个字符串切片,两个都是引用变量,而非字符串。请参考第 4 章中的 [作为函数参数的字符串切片](Ch04_Understanding_Ownership.md#string-slices-as-parameters) 小节,了解更多为何在清单 10-19 中使用的参数,即为这里想要的参数的讨论。
留意到由于这里并不打算这个 `longest` 函数取得其参数的所有权,因此这里是要该函数取两个字符串切片,两个都是引用变量,而非字符串。请参考第 4 章中的 [作为函数参数的字符串切片](Ch04_Understanding_Ownership.md#字符串切片作为函数参数) 小节,了解更多为何在清单 10-19 中使用的参数,即为这里想要的参数的讨论。
在尝试如下面清单 10-20 中所示的那样,对这个 `longest` 函数加以实现时,那将仍不会编译。

View File

@ -28,7 +28,7 @@
**The Anatomy of a Test Function**
Rust 最简单形态的测试,就是以 `test` 属性注解的一个函数。所谓属性,是指有关 Rust 代码片段的元数据attributes are metadata about pieces of Rust code在第 5 章中,[用在结构体上的 `derive` 属性](Ch05_Using_Structs_to_Structure_Related_Data.md#adding-useful-functionality-with-derived-traits),就是一个属性的例子。要将某个函数修改为测试函数,就要把 `#[test]` 添加在 `fn` 之前的行上。在以 `cargo test` 命令运行编写的测试时Rust 就会构建一个运行这些注解过的函数并就各个测试函数是否通过或失败进行汇报的测试运行器二进制文件a test runner binary
Rust 最简单形态的测试,就是以 `test` 属性注解的一个函数。所谓属性,是指有关 Rust 代码片段的元数据attributes are metadata about pieces of Rust code在第 5 章中,[用在结构体上的 `derive` 属性](Ch05_Using_Structs_to_Structure_Related_Data.md#使用派生特质加入有用功能),就是一个属性的例子。要将某个函数修改为测试函数,就要把 `#[test]` 添加在 `fn` 之前的行上。在以 `cargo test` 命令运行编写的测试时Rust 就会构建一个运行这些注解过的函数并就各个测试函数是否通过或失败进行汇报的测试运行器二进制文件a test runner binary
每当用 Cargo 构造了一个新的库项目时,就会自动生成有着一个测试函数的测试模组。该模组给到了编写测试的模板,如此以来,就不必在每次开始新项目时,去找寻确切的测试结构及语法了。而至于要添加多少个额外测试函数与测试模组,则取决于咱们自己!
@ -94,11 +94,11 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
Cargo 编译并运行了这个测试。这里看到那行 `running 1 test`。接下来的行就给出了那个自动生成测试函数的名字,名为 `it_works`,以及运行那个测试的结果为 `ok`。整体结论 `test result: ok.` 就表示全部测试都通过了,而后面的 `1 passed; 0 failed` 的部分,则对通过与未通过的测试数据,做了合计。
将某个测试标记为忽略,进而其在特定实例中不运行,是可能的;在本章后面的 ["忽视某些在特别要求下才运行的测试Ignoring Some Tests Unless Specifically Requested"](#ignoring-some-tests-unless-specifically-requested) 小节,就会讲到这个问题。由于这里尚未完成这个问题,因此这里的测试总结,就给出了 `0 ignored`。这里还可以把一个参数,传递给这个 `cargo test` 命令,来只测试那些名字与某个字符串匹配的测试;此特性叫做 *过滤filtering*,在 [“通过指定测试名字运行测试子集Running a Subset of Tests](#running-a-subset-of-tests) 小节,就会讲到这个问题。而这里也没有对所运行的测试加以过滤,因此在该测试小结的最后,显示了 `0 filtered out`
将某个测试标记为忽略,进而其在特定实例中不运行,是可能的;在本章后面的 ["忽视某些在特别要求下才运行的测试Ignoring Some Tests Unless Specifically Requested"](#在未作特别要求时忽略某些测试) 小节,就会讲到这个问题。由于这里尚未完成这个问题,因此这里的测试总结,就给出了 `0 ignored`。这里还可以把一个参数,传递给这个 `cargo test` 命令,来只测试那些名字与某个字符串匹配的测试;此特性叫做 *过滤filtering*,在 [“通过指定测试名字运行测试子集Running a Subset of Tests](#依据测试名称来运行测试的某个子集) 小节,就会讲到这个问题。而这里也没有对所运行的测试加以过滤,因此在该测试小结的最后,显示了 `0 filtered out`
其中属于基准测试的 `0 measured` 统计值对性能进行了测量。所谓基准测试benchmark tests就跟其字面意思一样只在每日构建版的 Rust 中可用。请参阅 [基准测试相关文档](https://doc.rust-lang.org/unstable-book/library-features/test.html) 了解更多信息。
测试输出接下来的部分,是以 `Doc-tests adder` 开始的,在有文档测试时,这便是文档测试的输出。虽然目前尚无文档测试,当 Rust 是可以编译在 API 文档中的全部代码示例的。此特性有助于将文档与代码保持同步!在第 14 章的 [“作为测试的文档注释Documentation Comments as Tests](Ch14_More_about_Cargo_and_Crates_io.md#documentation-comments-as-tests) 小节,就会讨论怎样编写文档测试。至于现在,就会这个 `Doc-tests` 的输出加以忽略。
测试输出接下来的部分,是以 `Doc-tests adder` 开始的,在有文档测试时,这便是文档测试的输出。虽然目前尚无文档测试,当 Rust 是可以编译在 API 文档中的全部代码示例的。此特性有助于将文档与代码保持同步!在第 14 章的 [“作为测试的文档注释Documentation Comments as Tests](Ch14_More_about_Cargo_and_Crates_io.md#作为测试的文档注释) 小节,就会讨论怎样编写文档测试。至于现在,就会这个 `Doc-tests` 的输出加以忽略。
接下来开始将该测试,定制为咱们自己所需的样子。首先将其中的 `it_works` 函数的名字,修改到某个别的名字,比如 `exploration`,像下面这样:
@ -190,7 +190,7 @@ error: test failed, to rerun pass '--lib'
*清单 11-4在一项测试通过而一项测试失败时的测试输出*
这里不再是 `ok` 了,`test tests::another` 那行给出了 `FAILED`。在这单独结果与测试小结直接,出现了两个新的部分:第一部分显示各个测试失败的具体原因。在此示例中,就得到 `another` 失败详情,是由于该测试函数在 `src/lib.rs` 文件第 15 行处 `panicked at '令该测试失败'`。接下来的部分,则列出了仅所有失败测试的名字,这在有很多测试,进而有很多详细失败测试输出时,是有用的。随后就可以使用某个失败测试的名字,来只运行该项测试而更容易地对其加以调试;在 [“对测试运行方式进行控制Controlling How Tests Are Run](#controlling-how-tests-are-run) 小节,将对运行测试方式,进行深入讲解。
这里不再是 `ok` 了,`test tests::another` 那行给出了 `FAILED`。在这单独结果与测试小结直接,出现了两个新的部分:第一部分显示各个测试失败的具体原因。在此示例中,就得到 `another` 失败详情,是由于该测试函数在 `src/lib.rs` 文件第 15 行处 `panicked at '令该测试失败'`。接下来的部分,则列出了仅所有失败测试的名字,这在有很多测试,进而有很多详细失败测试输出时,是有用的。随后就可以使用某个失败测试的名字,来只运行该项测试而更容易地对其加以调试;在 [“对测试运行方式进行控制Controlling How Tests Are Run](#控制测试以何种方式运行) 小节,将对运行测试方式,进行深入讲解。
显示在最后的测试小节行:总体上看,这个测试的结果为 `FAILED`。这里有一个测试通过,以及一个测试失败了。
@ -249,7 +249,7 @@ mod tests {
*清单 11-6`can_hold` 的一个检查较大矩形是否能够真正包含较小矩形的测试*
请注意这里在 `tests` 模组里头添加了个新行:`use super::*;`。这个 `tests` 模组是个遵循第 7 章中,[“用于指向模组树中某个项目的路径”](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#paths-for-referring-to-an-item-in-the-module-tree)小节中曾讲到一般可见性规则的常规模组。由于这个 `tests` 模组是个内部模组,因此这里就需要将外层模组中的受测试代码,带入到这个 `tests` 内部模组的作用域。而由于这里使用了一个全局通配符a glob, `*`),因此所有在外层模组中定义的内容,就对这个 `tests` 模组可用了。
请注意这里在 `tests` 模组里头添加了个新行:`use super::*;`。这个 `tests` 模组是个遵循第 7 章中,[“用于指向模组树中某个项目的路径”](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#用于引用目录树中项目的路径)小节中曾讲到一般可见性规则的常规模组。由于这个 `tests` 模组是个内部模组,因此这里就需要将外层模组中的受测试代码,带入到这个 `tests` 内部模组的作用域。而由于这里使用了一个全局通配符a glob, `*`),因此所有在外层模组中定义的内容,就对这个 `tests` 模组可用了。
这里已将这个测试命名为了 `larger_can_hold_smaller`,并创建除了所需的两个 `Rectanble` 实例。随后就调用了 `assert!` 宏,并将调用 `larger.can_hold(&smaller)` 的结果传递给了他。这个表达式应返回 `true`,因此这个测试将通过。那么就来试试看吧!
@ -460,13 +460,13 @@ error: test failed, to rerun pass '--lib'
`assert_ne!` 宏则将在给到其两个不相等值时通过测试,在两个值相等时测试失败。对于在不确定某个值是什么,但却清楚该值明显不会为何时的各种情形,这个宏就是最有用的。比如,在对某个确切会以某种方式修改其输入的函数进行测试,而修改方式会根据具体每周的哪一天运行该测试发生改变时,那么加以断言的最佳事物,就会是该函数的输出,与其输入不相等。
表象之下,`assert_eq!` 与 `assert_ne!` 两个宏,分别使用了运算符 `==``!=`。在他们的断言失败时这两个宏就会使用调试格式化debug formatting将他们的参数打印出来这就意味着正被比较的两个值必须实现了 `PartialEq``Debug` 特质。全部原生值与绝大多数的标准库类型,都实现了这两个特质。而对于咱们自己定义的结构体与枚举,就需要实现 `PartialEq` 来对这些类型的相等与否进行断言。同样还需要实现 `Debug`,来在断言失败时打印比较的两个值。由于这两个特质都正如第 5 章清单 5-12 中所提到的派生特质derivable traits这样就跟将 `#[derive(PartialEq, Debug)]` 注解,添加到所编写的结构体或枚举定义一样直接了。请参阅附录 C[“可派生特质derivable traits”](Ch21_Appdendix.md#c-derivable-traits) 了解更多有关这两个及其他派生特质的详细信息。
表象之下,`assert_eq!` 与 `assert_ne!` 两个宏,分别使用了运算符 `==``!=`。在他们的断言失败时这两个宏就会使用调试格式化debug formatting将他们的参数打印出来这就意味着正被比较的两个值必须实现了 `PartialEq``Debug` 特质。全部原生值与绝大多数的标准库类型,都实现了这两个特质。而对于咱们自己定义的结构体与枚举,就需要实现 `PartialEq` 来对这些类型的相等与否进行断言。同样还需要实现 `Debug`,来在断言失败时打印比较的两个值。由于这两个特质都正如第 5 章清单 5-12 中所提到的派生特质derivable traits这样就跟将 `#[derive(PartialEq, Debug)]` 注解,添加到所编写的结构体或枚举定义一样直接了。请参阅附录 C[“可派生特质derivable traits”](Ch21_Appdendix.md#附录-c派生特质) 了解更多有关这两个及其他派生特质的详细信息。
### 加入定制失败消息
**Adding Custom Failure Message**
还可将与失败消息一同打印的定制消息,作为 `assert!`、`assert_eq!` 及 `assert_ne!` 宏的可选参数加入进来。在必须的两个参数之后指定的全部参数,都被传递给他们中的 `format!` 宏(第 8 章中 [“以 `+` 操作符或 `format!` 宏的字符串连接Concatenation with the `+` Operator or the `format!` macro](Ch08_Common_Collections.md#concatenation-with-the-plus-operator-or-the-format-macro) 小节曾讲到),因此就可以传递一个包含了 `{}` 占位符的格式化字符串,以及进到这些占位符的值。对于给某个断言表示什么的文档编制,这些定制消息就是有用的;在某个测试失败时,就会有着该代码下那个问题的较好理解。
还可将与失败消息一同打印的定制消息,作为 `assert!`、`assert_eq!` 及 `assert_ne!` 宏的可选参数加入进来。在必须的两个参数之后指定的全部参数,都被传递给他们中的 `format!` 宏(第 8 章中 [“以 `+` 操作符或 `format!` 宏的字符串连接Concatenation with the `+` Operator or the `format!` macro](Ch08_Common_Collections.md#使用--运算符或-format-宏的字符串连接) 小节曾讲到),因此就可以传递一个包含了 `{}` 占位符的格式化字符串,以及进到这些占位符的值。对于给某个断言表示什么的文档编制,这些定制消息就是有用的;在某个测试失败时,就会有着该代码下那个问题的较好理解。
比如说,这里有个按照名字来打招呼的函数,并打算就传入到该函数的名字有出现在输出中进行测试:
@ -1223,7 +1223,7 @@ mod tests {
*清单 11-12对私有函数进行测试*
请注意这个 `internal_adder` 函数,未被标记为 `pub`。其中的那些测试,都只是些 Rust 代码,同时那个 `tests` 模组,只是另一个模组。如同在前面的 [“用于对模组树中某个项目进行引用的路径”](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#paths-for-referring-to-an-item-in-the-module-tree) 小节中所讨论的,子模组中的那些项目,可以使用其祖辈模组中的项目。在这个测试中,就以 `use super::*` 语句,将那个 `tests` 模组父辈的那些项目,带入到了作用域,进而该测试随后就可以调用 `internal_adder` 了。而在不认为私有函数应被测试时,那么 Rust 就没有什么可以迫使你对他们进行测试了if you don't think private functions should be tested, there's nothing in Rust that will compel you to do so
请注意这个 `internal_adder` 函数,未被标记为 `pub`。其中的那些测试,都只是些 Rust 代码,同时那个 `tests` 模组,只是另一个模组。如同在前面的 [“用于对模组树中某个项目进行引用的路径”](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#用于引用目录树中项目的路径) 小节中所讨论的,子模组中的那些项目,可以使用其祖辈模组中的项目。在这个测试中,就以 `use super::*` 语句,将那个 `tests` 模组父辈的那些项目,带入到了作用域,进而该测试随后就可以调用 `internal_adder` 了。而在不认为私有函数应被测试时,那么 Rust 就没有什么可以迫使你对他们进行测试了if you don't think private functions should be tested, there's nothing in Rust that will compel you to do so
### 集成测试
@ -1320,9 +1320,9 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
**集成测试中的子模组**
随着更多集成测试的添加,就会想要在那个 `tests` 目录下,构造更多文件,来帮助组织这些文件;比如就可以将那些测试函数,按照他们所测试的功能而进行分组。如同早先所提到的,在 `tests` 目录下的各个文件,都作为其自己单独的代码箱而被编译,这一点对于创建独立作用域,来对最终用户将要使用所编写代码箱的方式,进行更紧密模拟是有用的。不过,这将意味着在 `tests` 目录中的那些文件,不会如同在第 7 章中,有关 [如何将代码分离为模组与文件](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#separating-modules-into-different-files) 部分,所掌握的 `src` 中的那些文件那样,共用同样的行为。
随着更多集成测试的添加,就会想要在那个 `tests` 目录下,构造更多文件,来帮助组织这些文件;比如就可以将那些测试函数,按照他们所测试的功能而进行分组。如同早先所提到的,在 `tests` 目录下的各个文件,都作为其自己单独的代码箱而被编译,这一点对于创建独立作用域,来对最终用户将要使用所编写代码箱的方式,进行更紧密模拟是有用的。不过,这将意味着在 `tests` 目录中的那些文件,不会如同在第 7 章中,有关 [如何将代码分离为模组与文件](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#将模组拆分为不同文件) 部分,所掌握的 `src` 中的那些文件那样,共用同样的行为。
在有着一套在多个集成测试文件中使用的辅助函数,并尝试遵循第 7 章 [将模组分离为不同文件](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#separating-modules-into-different-files) 中的步骤,把这些辅助函数提取到某个通用模组中时,`tests` 目录的那些文件的不同行为就最为明显了。比如说,在创建出 `tests/common.rs` 并将一个名为 `setup` 的函数放在其中时,就可以将一些要在多个测试文件的多个测试函数调用的代码,添加到 `setup`
在有着一套在多个集成测试文件中使用的辅助函数,并尝试遵循第 7 章 [将模组分离为不同文件](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#将模组拆分为不同文件) 中的步骤,把这些辅助函数提取到某个通用模组中时,`tests` 目录的那些文件的不同行为就最为明显了。比如说,在创建出 `tests/common.rs` 并将一个名为 `setup` 的函数放在其中时,就可以将一些要在多个测试文件的多个测试函数调用的代码,添加到 `setup`
文件名:`tests/common.rs`
@ -1382,7 +1382,7 @@ adder
└── integration_test.rs
```
这是曾在第 7 章 ["替代文件路径"](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#alternate-file-paths) 小节所提到的Rust 同样明白的较早命名约定。以这种方式命名该文件,就告诉 Rust 不要将那个 `common` 模组,作为一个集成测试文件对待。在将这个 `setup` 函数移入到 `tests/common/mod.rs` 里头,并删除了那个 `tests/common.rs` 文件时,在测试输出中的该部分就不再出现了。`tests` 目录子目录中的那些文件,不会作为单独代码箱而被编译,也不会在测试输出中拥有自己的部分。
这是曾在第 7 章 ["替代文件路径"](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#备用文件路径) 小节所提到的Rust 同样明白的较早命名约定。以这种方式命名该文件,就告诉 Rust 不要将那个 `common` 模组,作为一个集成测试文件对待。在将这个 `setup` 函数移入到 `tests/common/mod.rs` 里头,并删除了那个 `tests/common.rs` 文件时,在测试输出中的该部分就不再出现了。`tests` 目录子目录中的那些文件,不会作为单独代码箱而被编译,也不会在测试输出中拥有自己的部分。
在创建出 `tests/common/mod.rs` 之后,就可以从任意的集成测试文件,将其作为模组而加以使用。下面就是一个从 `tests/integration_test.rs` 中的 `it_adds_two` 测试,对这个 `setup` 函数进行调用的示例:

View File

@ -58,7 +58,7 @@ fn main() {
*清单 12-1将命令行参数收集到一个矢量中并把他们打印出来*
这里首先使用了一个 `use` 语句,将那个 `std::env` 模组带入到了作用域,如此就可以使用他的 `args` 函数了。请注意这个 `std::env::args` 函数,是嵌套在两个层级的模组中的。如同在 [第 7 章](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#creating-idiomatic-use-path) 处所讨论过的,在那些所需函数是嵌套于多个模组中的情形下,那里就选择将其中的父模组带入到作用域,而非该函数本身。经由这样做,就可以轻易地使用到 `std::env` 中的其他函数了。同时相比于添加 `use std::env::args` 并在随后只使用 `args` 调用这个函数,这样做也不那么含糊其辞,这是由于 `args` 这个名字,可能稍不留意就会被误用为定义在当前模组中的某个函数。
这里首先使用了一个 `use` 语句,将那个 `std::env` 模组带入到了作用域,如此就可以使用他的 `args` 函数了。请注意这个 `std::env::args` 函数,是嵌套在两个层级的模组中的。如同在 [第 7 章](Ch07_Managing_Growing_Projects_with_Packages_Crates_and_Modules.md#创建惯用-use-路径) 处所讨论过的,在那些所需函数是嵌套于多个模组中的情形下,那里就选择将其中的父模组带入到作用域,而非该函数本身。经由这样做,就可以轻易地使用到 `std::env` 中的其他函数了。同时相比于添加 `use std::env::args` 并在随后只使用 `args` 调用这个函数,这样做也不那么含糊其辞,这是由于 `args` 这个名字,可能稍不留意就会被误用为定义在当前模组中的某个函数。
> **`args` 函数与无效 Unicode 字符**
>
@ -394,7 +394,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
*清单 12-8添加一个参数个数的检查*
此代码与 [清单 9-13 中曾编写过的 `Guess::new` 函数](Ch09_Error_Handling.md#creating-custom-types-for-validation) 类似,其中在那个 `value` 参数超出有效值边界时,就调用了 `panic!` 宏。这里没有检查值的边界,而是就 `args` 的长度至少为 `3` 进行了检查,进而该函数的其余部分,就可以在此条件已满足的假定下运作了。在 `args` 所拥有的条目少于三个时,此条件便为真,进而这里就会条约那个 `panic!` 宏,来立即结束这个程序。
此代码与 [清单 9-13 中曾编写过的 `Guess::new` 函数](Ch09_Error_Handling.md#创建用于验证的定制类型) 类似,其中在那个 `value` 参数超出有效值边界时,就调用了 `panic!` 宏。这里没有检查值的边界,而是就 `args` 的长度至少为 `3` 进行了检查,进而该函数的其余部分,就可以在此条件已满足的假定下运作了。在 `args` 所拥有的条目少于三个时,此条件便为真,进而这里就会条约那个 `panic!` 宏,来立即结束这个程序。
有了`new` 中的这些额外少数几行,下面就不带任何参数地再度运行这个程序,来看看现在错误看起来如何:
@ -407,7 +407,7 @@ thread 'main' panicked at '参数数量不足', src/main.rs:25:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
此输出好了一些:现在这里就有了一个合理的错误消息了。不过,这里还有一些不希望给到用户的无关信息。或许运用曾在清单 9-13 中用到的那种技巧,并非这里要用到的最佳技巧:到 `panic!` 的调用,相比于用法方面的问题,是更适合于编程方面的问题的,如同 [第 9 章中所讨论的那样](Ch09_Error_Handling.md#guidelines-for-error-handling)。相反,这里将使用之前在第 9 章中曾学到的另一项技能 -- [返回一个 `Result`](Ch09_Error_Handling.md#recoverable-errors-with-result),以表示成功执行成功或是出错。
此输出好了一些:现在这里就有了一个合理的错误消息了。不过,这里还有一些不希望给到用户的无关信息。或许运用曾在清单 9-13 中用到的那种技巧,并非这里要用到的最佳技巧:到 `panic!` 的调用,相比于用法方面的问题,是更适合于编程方面的问题的,如同 [第 9 章中所讨论的那样](Ch09_Error_Handling.md#错误处理守则)。相反,这里将使用之前在第 9 章中曾学到的另一项技能 -- [返回一个 `Result`](Ch09_Error_Handling.md#带有-result-的可恢复错误),以表示成功执行成功或是出错。
**返回一个 `Result` 值,而非调用 `panic!` 宏**
@ -482,7 +482,7 @@ $ cargo run  ✔
**Extract Logic from `main`**
既然这里已经完成了对配置解析的重构,那么就来转向该程序的逻辑部分。如同在 [“二进制项目的关注点分离”](#separation-of-concerns-for-binary-projects) 小节中所指出的,这里将提取出一个保有当前在这个 `main` 函数中,不涉及到建立配置与错误处理的全部逻辑的 `run` 函数。在完成此过程时,`main` 就变得简洁而易于经由目测得以验证,并能编写出全部其他逻辑的测试。
既然这里已经完成了对配置解析的重构,那么就来转向该程序的逻辑部分。如同在 [“二进制项目的关注点分离”](#二进制程序项目的关注点分离) 小节中所指出的,这里将提取出一个保有当前在这个 `main` 函数中,不涉及到建立配置与错误处理的全部逻辑的 `run` 函数。在完成此过程时,`main` 就变得简洁而易于经由目测得以验证,并能编写出全部其他逻辑的测试。
下面清单 12-11 给出了那个被提取出的 `run` 函数。此时,这里只进行小的、渐进式的提出该函数的改进。此时仍将该函数定义在 `src/main.rs` 中。
@ -538,7 +538,7 @@ fn run(config: Config) -> Result<(), Box<dyn Error>>{
而对于错误类型这里使用了那个特质对象the `trait object` `Box<dyn Error>` (且这里已在代码顶部,使用一条 `use` 语句,而已将 `std::error::Error` 带入到了作用域)。这里将在 [第 17 章](Ch17_Object_Oriented_Programming_Features_of_Rust.md) 讲到特质对象。至于现在,则只要了解那个 `Box<(), Error>` 表示该函数将返回一个实现了 `Error` 特质的类型,而这里不必指明该返回值将是何种特定类型。这就给到了在不同错误情形下,返回值可能为不同类型的灵活性。这个 `dyn` 关键字,是 “动态dynamic” 的缩写。
其次,这里通过使用那个 `?` 运算符,而已将到 `expect` 的调用移除,正如在 [第 9 章](Ch09_Error_Handling.md#a-shortcut-for-propagating-errors-the-question-mark-operator) 中曾讲到过的那样。与在某个错误上 `panic!` 不同,`?` 将返回把当前函数中的错误值,返回给调用者来加以处理。
其次,这里通过使用那个 `?` 运算符,而已将到 `expect` 的调用移除,正如在 [第 9 章](Ch09_Error_Handling.md#传递错误的快捷方式-操作符) 中曾讲到过的那样。与在某个错误上 `panic!` 不同,`?` 将返回把当前函数中的错误值,返回给调用者来加以处理。
第三,这个 `run` 函数现在会在成功情形下返回一个 `Ok` 值。在函数签名中,这里已将该 `run` 函数的成功类型定义为 `()`,这就意味着需要将那个单元值,封装在 `Ok` 值中。乍一看这个 `Ok(())` 语法或许有点陌生,不过像这样使用 `()`,则正是一种表明这里调用 `run` 只是为了其副作用的方式;他不会返回一个这里所需要的值。
@ -685,7 +685,7 @@ fn main() {
### 编写一个失败测试
由于不再需要 `src/lib.rs``src/main.rs` 中的那些,曾用于对该程序行为加以检查的 `println!` 语句,因此这里就要将其移出掉。随后,就要在 `src/lib.rs` 中,添加带有一个测试函数的 `tests` 模组,就跟曾在 [第 11 章](Ch11_Writing_Automated_Tests.md#the-anatomy-of-a-test-function) 曾做过的那样。该测试函数指明了这里所打算的这个 `search` 函数要有的行为:他将取得一个查询字串,与要搜索的文本,同时他将只返回搜索文本中,包含了查询字串的那些行。下面清单 12-15 给出了这个测试,该清单尚不会编译。
由于不再需要 `src/lib.rs``src/main.rs` 中的那些,曾用于对该程序行为加以检查的 `println!` 语句,因此这里就要将其移出掉。随后,就要在 `src/lib.rs` 中,添加带有一个测试函数的 `tests` 模组,就跟曾在 [第 11 章](Ch11_Writing_Automated_Tests.md#测试函数剖析) 曾做过的那样。该测试函数指明了这里所打算的这个 `search` 函数要有的行为:他将取得一个查询字串,与要搜索的文本,同时他将只返回搜索文本中,包含了查询字串的那些行。下面清单 12-15 给出了这个测试,该清单尚不会编译。
文件名:`src/lib.rs`
@ -723,7 +723,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{
*清单 12-16定义出一个刚好让这里测试编译的那个 `search` 函数来*
请注意这里需要在 `search` 的函数签名中,定义一个显式的生命周期 `'a`,并在 `contents` 参数与返回值上,使用那个生命周期。回顾 [第 10 章](Ch10_Generic_Types_Traits_and_Lifetimes.md#validating-references-with-lifetimes) 中讲到,这些生命周期参数指明了哪个参数生命周期,是与返回值生命周期联系起来的。在这个示例中,这就表示那个返回的矢量,应包含引用了参数 `contents` (而非参数 `query`)的一些切片的字符串切片。
请注意这里需要在 `search` 的函数签名中,定义一个显式的生命周期 `'a`,并在 `contents` 参数与返回值上,使用那个生命周期。回顾 [第 10 章](Ch10_Generic_Types_Traits_and_Lifetimes.md#使用生命周期对引用加以验证) 中讲到,这些生命周期参数指明了哪个参数生命周期,是与返回值生命周期联系起来的。在这个示例中,这就表示那个返回的矢量,应包含引用了参数 `contents` (而非参数 `query`)的一些切片的字符串切片。
也就是说,这里告诉 Rust`search` 函数返回的数据,将存活到与传递给那个 `search` 函数的、在 `contents` 参数中数据同样长时间。这是相当重要的!*为* 某个切片所引用的数据,需要在该引用有效期间保持有效;若编译器假定这里是在构造 `query` 而非 `contents` 的字符串切片,那么他就会执行错误地安全性检查。
@ -751,7 +751,7 @@ error: could not compile `minigrep` due to previous error
Rust 是不可能明白,这里需要的到底是两个参数中哪一个的,因此这里就需要显式地告诉 Rust。而由于 `contents` 正是那个包含了这里全部文本的参数,而这里打算返回的,就是与那个文本匹配的部分,因此这里清楚 `contents` 就应是要运用生命周期语法,将其与返回值联系起来的那个参数。
别的编程语言并不会要求在函数签名中,将参数与返回值联系起来,但随着时间的推移,这样的实践将变得容易起来。或许你会将这个示例,与第 10 章中的 [“使用生命周期对引用进行验证” 小节](Ch10_Generic_Types_Traits_and_Lifetimes.md#validating-references-with-lifetimes) 加以比较。
别的编程语言并不会要求在函数签名中,将参数与返回值联系起来,但随着时间的推移,这样的实践将变得容易起来。或许你会将这个示例,与第 10 章中的 [“使用生命周期对引用进行验证” 小节](Ch10_Generic_Types_Traits_and_Lifetimes.md#使用生命周期对引用加以验证) 加以比较。
现在来运行测试:
@ -812,7 +812,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{
*清单 12-17遍历 `contents` 中的各行*
这个 `lines` 方法返回的是个迭代器an iterator。在 [第 13 章](Ch13_Functional_Language_Features_Iterators_and_Closures.md#processing-a-series-of-items-with-iterators) 中,就会讲到迭代器,不过回顾一下 [清单 3-5](Ch03_Common_Programming_Concepts.md#looping-through-a-collection-with-for) 中,就曾见过这种用到迭代器的方式,那里曾用到一个 `for` 循环, 就带有一个用于在集合中各个元素上,运行某些代码的迭代器。
这个 `lines` 方法返回的是个迭代器an iterator。在 [第 13 章](Ch13_Functional_Language_Features_Iterators_and_Closures.md#使用迭代器对条目系列进行处理) 中,就会讲到迭代器,不过回顾一下 [清单 3-5](Ch03_Common_Programming_Concepts.md#使用-for-对集合进行遍历) 中,就曾见过这种用到迭代器的方式,那里曾用到一个 `for` 循环, 就带有一个用于在集合中各个元素上,运行某些代码的迭代器。
**在各行中搜索那个查询字串**
@ -887,7 +887,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
这里的测试通过了,进而咱们就明白 `search` 函数是工作的了!
到这里,咱们就会在保持这些测试通过,以维持这同样功能的同时,考虑对这个 `search` 函数的实现,进行重构的一些机会。这个 `search` 函数中的代码虽不怎么差劲,但他并没有利用上迭代器的一些有用特性。在 [第 13 章](Ch13_Functional_Language_Features_Iterators_and_Closures.md#processing-a-series-of-items-with-iterators) 中将回到这个示例,那里就会详细探讨到迭代器,进而会看看怎样来改进这个 `search` 函数。
到这里,咱们就会在保持这些测试通过,以维持这同样功能的同时,考虑对这个 `search` 函数的实现,进行重构的一些机会。这个 `search` 函数中的代码虽不怎么差劲,但他并没有利用上迭代器的一些有用特性。在 [第 13 章](Ch13_Functional_Language_Features_Iterators_and_Closures.md#使用迭代器对条目系列进行处理) 中将回到这个示例,那里就会详细探讨到迭代器,进而会看看怎样来改进这个 `search` 函数。
**在 `run` 函数中使用这个 `search` 函数**

View File

@ -834,7 +834,7 @@ impl Config {
`env::args` 函数的标准库文档显示,其所返回的迭代器类型为 `std::env::Args`,而那个类型是实现了 `Iterator` 特质的,同时返回的是一些 `String` 值。
这里已经更新了这个 `Config::build` 函数的签名,那么参数 `args`,就有了一个带有特质边界 `impl Iterator<Item = String>` 的泛型,而不再是 `&[String]` 类型了。第 10 章 [作为参数的特质](Ch10_Generic_Types_Traits_and_Lifetimes.md#traits-as-paramters) 小节曾讨论过的这种 `impl Trait` 语法的用法,表示 `args` 可以是任何实现了 `Iterator` 类型,并返回一些 `String` 条目的类型。
这里已经更新了这个 `Config::build` 函数的签名,那么参数 `args`,就有了一个带有特质边界 `impl Iterator<Item = String>` 的泛型,而不再是 `&[String]` 类型了。第 10 章 [作为参数的特质](Ch10_Generic_Types_Traits_and_Lifetimes.md#作为参数的特质) 小节曾讨论过的这种 `impl Trait` 语法的用法,表示 `args` 可以是任何实现了 `Iterator` 类型,并返回一些 `String` 条目的类型。
由于这里取得了 `args` 的所有权,且这里通过对 `args` 进行迭代,而将对其进行修改,因此这里可把 `mut` 关键字,添加到这个 `args` 参数的说明中,来将其构造为可变的。

View File

@ -105,7 +105,7 @@ pub fn add_one(x: i32) -> i32 {
*图 14-01`add_one` 函数的 HTML 文档*
**经常用到的一些小节**
#### 经常用到的一些小节
这里用到了清单 14-1 中 `# 示例examples` 的 Markdown 标题,来创建出生成 HTML 中,有着标题 “示例examples” 的一个小节。下面是代码箱编写者经常在他们文档中,用到的一些别的小节:
@ -117,7 +117,9 @@ pub fn add_one(x: i32) -> i32 {
多数的文档注释,并不需要全部的这些小节,但这仍不失为提醒咱们,代码使用者将有兴趣了解咱们代码哪些方面的一个不错的检查单。
**作为测试的文档注释Documentation Comments as Tests**
#### 作为测试的文档注释
**Documentation Comments as Tests**
在文档注释中添加一些代码块可有助于演示怎样使用咱们的库而这样做有着一项额外收获an additional bonus运行 `cargo test` 将以测试方式,运行文档中的那些代码示例!没有什么比带有示例的文档更好的了。然而比起由于在文档写好后,代码已被修改而造成的示例不工作,也没有什么更糟糕的了。在清单 14-1 中 `add_one` 函数的文档下,运行 `cargo test` 时,就会在测试结果中看到这样一个小节:

View File

@ -33,7 +33,7 @@
- 有着大量数据并打算在转移所有权的同时确保这些数据不会被拷贝时when you have a large amount of data and you want to transfer ownership but ensure the data won't be copied when you do so
- 在希望拥有某个值并只关心其为某个实现了特定特质的类型而非为某个具体类型when you want to own a value and you care only that it's a type that implements a particular trait rather than being of a specific type
这第一种情形将在 [使用匣子实现递归类型](#enabling-recursive-types-with-boxes) 小节演示。在第二种情形下,大量数据所有权的转移,会由于这些数据在栈上拷来拷去。为改进这种情形下的性能,就可以将这些大量数据存储在内存堆上的一个匣子中。随后,就只有少量的指针数据,在栈上拷贝了,同时其引用的数据,还是呆在堆上的一个地方。第三种情形被称为 *特质对象* *trait object*),而第 17 章中,用了一整个小节,[“使用实现具有多个类型值的特质对象”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#using-trait-objects-that-allow-for-values-of-different-types),来只讲解那个方面。因此这里掌握的东西,还会在第 17 章中用到。
这第一种情形将在 [使用匣子实现递归类型](#使用匣子数据结构实现递归数据类型) 小节演示。在第二种情形下,大量数据所有权的转移,会由于这些数据在栈上拷来拷去。为改进这种情形下的性能,就可以将这些大量数据存储在内存堆上的一个匣子中。随后,就只有少量的指针数据,在栈上拷贝了,同时其引用的数据,还是呆在堆上的一个地方。第三种情形被称为 *特质对象* *trait object*),而第 17 章中,用了一整个小节,[“使用实现具有多个类型值的特质对象”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#使用允许不同类型值的特质对象),来只讲解那个方面。因此这里掌握的东西,还会在第 17 章中用到。
### 使用 `Box<T>` 在内存堆上存储数据
@ -349,7 +349,7 @@ error: could not compile `sp_demos` due to previous error
**Treating a Type Like a Reference by Implementing the `Deref` Trait**
正如第 10 章的 ["在类型上实现某个特质"](Ch10_Generic_Types_Traits_and_Lifetimes.md#implementing-a-trait-on-a-type) 小节中所讨论过的,这里需要提供到特质所要求的那些方法的实现。而这个由标准库提供的 `Deref` 特质,要求咱们实现一个会借用到 `self`,并会返回到其内部数据的引用的名为 `deref` 的方法。下面清单 15-10 包含了添加到 `MyBox` 定义的一个 `Deref` 实现:
正如第 10 章的 ["在类型上实现某个特质"](Ch10_Generic_Types_Traits_and_Lifetimes.md#在类型上实现某个特质) 小节中所讨论过的,这里需要提供到特质所要求的那些方法的实现。而这个由标准库提供的 `Deref` 特质,要求咱们实现一个会借用到 `self`,并会返回到其内部数据的引用的名为 `deref` 的方法。下面清单 15-10 包含了添加到 `MyBox` 定义的一个 `Deref` 实现:
文件名:`src/main.rs`
@ -377,7 +377,7 @@ impl<T> Deref for MyBox<T> {
其中 `type Target = T;` 这种语法,定义出了 `Deref` 特质要用到的一个关联类型an assiotiated type for the `Deref` trait to use。关联类型属于与声明泛型参数有些许不同的声明方式现在无需担心他们在第 19 张中将更细致地讲到他们。
这里填入 `deref` 方法函数体的是 `&self.0`,从而 `deref` 就返回了到咱们打算用 `*` 运算符访问的那个值的一个引用;回顾第 5 章的 [运用不带命名字段的元组结构体来创建出不同类型](Ch05_Using_Structs_to_Structure_Related_Data.md#using-tuple-structs-without-named-fields-to-create-different-types) 小节,那个 `.0` 就是访问了结构体中的首个值。清单 15-9 中在其中 `MyBox<T>` 值上调用了 `*``main` 函数,现在就会编译了,同时那些断言将通过!
这里填入 `deref` 方法函数体的是 `&self.0`,从而 `deref` 就返回了到咱们打算用 `*` 运算符访问的那个值的一个引用;回顾第 5 章的 [运用不带命名字段的元组结构体来创建出不同类型](Ch05_Using_Structs_to_Structure_Related_Data.md#使用不带命名字段的元组结构体来创建不同类型) 小节,那个 `.0` 就是访问了结构体中的首个值。清单 15-9 中在其中 `MyBox<T>` 值上调用了 `*``main` 函数,现在就会编译了,同时那些断言将通过!
没有这个 `Deref` 特质,编译器就只能解引用那些 `&` 的引用。那个 `deref` 方法,给到了编译器取得实现了 `Deref` 特质的任意类型值的能力,而调用该特质的 `deref` 方法,就获得了其知道如何解引用的一个 `&` 引用。
@ -710,7 +710,7 @@ fn main() {
这里本可以调用 `a.clone()` 而不是 `Rc::clone(&a)`但在这种情况下Rust 的约定就是使用 `Rc::clone`。`Rc::clone` 方法的实现,与绝大多数类型的 `clone` 实现方式不同,其并不会构造全部数据的深拷贝。到 `Rc::clone` 的调用,只会增加引用计数,这样做不耗费很多时间。而数据的一些深拷贝,则能耗费很多时间。通过使用 `Rc::clone` 来进行引用计数,咱们就可以直观地区别出深拷贝类别的那些克隆,与那些增加引用计数的克隆类别。在查找代码中的性能问题时,咱们只需要关注那些深拷贝的克隆,而可以不用管那些到 `Rc::clone` 的调用。
> **注**:第 4 章 [变量与数据交互方式之二:克隆](Ch04_Understanding_Ownership.md#ways-variables-and-data-interact-clone) 中,曾提到:“当看到一个对 clone 方法的调用时,那么就明白正有一些任性代码在被执行,且那代码可能开销高昂。对此方法的调用,是某些不同寻常事情正在发生的明显标志。”。
> **注**:第 4 章 [变量与数据交互方式之二:克隆](Ch04_Understanding_Ownership.md#变量与数据交互方式之二克隆) 中,曾提到:“当看到一个对 clone 方法的调用时,那么就明白正有一些任性代码在被执行,且那代码可能开销高昂。对此方法的调用,是某些不同寻常事情正在发生的明显标志。”。
### 对某个 `Rc<T>` 进行克隆,就会增加引用计数
@ -740,7 +740,7 @@ fn main() {
*清单 15-19打印出引用计数*
在程序中引用计数变化的各个点位,咱们都打印出了引用计数,其正是咱们经由调用 `Rc::strong_count` 函数得到的。该函数之所以名为 `strong_count`,而非 `count`,是由于这个 `Rc<T>` 类型,还有一个 `weak_count` 函数;在 [阻止引用的循环:将 `Rc<T>` 转换为 `Weak<T>`](#preventing-reference-cycles-turning-an-rc-t-into-a-weak-t) 小节,就会看到 `weak_count` 的使用。
在程序中引用计数变化的各个点位,咱们都打印出了引用计数,其正是咱们经由调用 `Rc::strong_count` 函数得到的。该函数之所以名为 `strong_count`,而非 `count`,是由于这个 `Rc<T>` 类型,还有一个 `weak_count` 函数;在 [阻止引用的循环:将 `Rc<T>` 转换为 `Weak<T>`](#防止引用循环将-rct-转变为-weakt) 小节,就会看到 `weak_count` 的使用。
此代码会打印出下面的东西:
@ -1113,7 +1113,7 @@ fn main() {
这里创建了为 `Rc<RefCell<i32>>` 类型实例的一个值,并将其存储在名为 `value` 的一个变量中,如此咱们就可以在稍后直接访问他。接着这里在 `a` 中,创建了有着保存了 `value``Cons` 变种的一个 `List`。这里需要克隆 `value`,这样 `a``value` 都会有着那个内层值 `5` 的所有权,而非将所有权从 `value` 转移到 `a` 或让 `a``value` 借用。
这里把那个列表 `a`,封装在了一个 `Rc<T>` 中,进而在创建列表 `b``c` 时,二者都可以引用到 `a`,正如咱们在清单 15-18 中所做的那样。在这里已创建出 `a`、`b` 与 `c` 中的三个列表后,就打算把 `10` 加到 `value` 中的那个值。咱们是通过调用 `value` 上的 `borrow_mut` 方法做到这点的,这用到了第 5 章中曾讨论过的自动解引用特性(参见 [`->` 操作符去哪儿了?](Ch05_Using_Structs_to_Structure_Related_Data.md#where-is-the-arrow-operator)),来将这个 `Rc<T>` 解引用到内层的 `RefCell<T>` 值。这个 `borrow_mut` 方法返回的是一个 `RefMut<T>` 的灵巧指针,而咱们于其上使用了解引用运算符,并修改了那个内层值。
这里把那个列表 `a`,封装在了一个 `Rc<T>` 中,进而在创建列表 `b``c` 时,二者都可以引用到 `a`,正如咱们在清单 15-18 中所做的那样。在这里已创建出 `a`、`b` 与 `c` 中的三个列表后,就打算把 `10` 加到 `value` 中的那个值。咱们是通过调用 `value` 上的 `borrow_mut` 方法做到这点的,这用到了第 5 章中曾讨论过的自动解引用特性(参见 [`->` 操作符去哪儿了?](Ch05_Using_Structs_to_Structure_Related_Data.md#--操作符the---operator哪去了呢)),来将这个 `Rc<T>` 解引用到内层的 `RefCell<T>` 值。这个 `borrow_mut` 方法返回的是一个 `RefMut<T>` 的灵巧指针,而咱们于其上使用了解引用运算符,并修改了那个内层值。
在打印 `a`、`b` 与 `c` 时,就可以看到他们都有了修改后的值 `15` 而非 `5`

View File

@ -128,7 +128,7 @@ fn main() {
*清单 16-2保存一个来自 `thread::spawn``JoinHandle` 来确保该线程运行完毕*
> **注**:结合第 9 章中 [因错误而中止的快捷方式:`unwrap` 与 `expect`](Ch09_Error_Handling.md#shortcuts-for-panic-on-error-unwrap-and-expect),表明 `join` 返回的是个 `Result<T, E>` 类型的枚举值。
> **注**:结合第 9 章中 [因错误而中止的快捷方式:`unwrap` 与 `expect`](Ch09_Error_Handling.md#因错误而中止的快捷方式unwrap-与-expect),表明 `join` 返回的是个 `Result<T, E>` 类型的枚举值。
在这个把手上调用 `join`,就会阻塞那个当前运行的线程,直到由该把手所表示的该线程终止。所谓 *阻塞blocking* 某个线程,是指那个线程被阻止执行工作或退出,*blocking* a thread means that thread is prevented from performing work or exiting。由于咱们已将到 `join` 的调用,放在了那个主线程的 `for` 循环之后,因此运行清单 16-2 中的代码,应产生出如下类似的输出(注:但每次运行的输出仍然不同):
@ -200,7 +200,7 @@ fn main() {
**Using `move` Closures with Threads**
由于传递给 `thread::spawn` 的闭包随后将取得其用到的环境中一些值的所有权,由此就会把这些值的所有权,从一个线程转移到另一线程,因此咱们今后将经常在这些闭包上,使用 `move` 关键字。在第 13 章 [“捕获引用或迁移所有权”](Ch13_Functional_Language_Features_Iterators_and_Closures.md#capturing-reference-or-moving-ownership) 小节,咱们就曾讨论过闭包语境下的 `move` 关键字。现在,咱们将更多地着重于 `move``thread::spawn` 之间的互动。
由于传递给 `thread::spawn` 的闭包随后将取得其用到的环境中一些值的所有权,由此就会把这些值的所有权,从一个线程转移到另一线程,因此咱们今后将经常在这些闭包上,使用 `move` 关键字。在第 13 章 [“捕获引用或迁移所有权”](Ch13_Functional_Language_Features_Iterators_and_Closures.md#捕获引用抑或迁移所有权) 小节,咱们就曾讨论过闭包语境下的 `move` 关键字。现在,咱们将更多地着重于 `move``thread::spawn` 之间的互动。
请注意在清单 16-1 中,传递给 `thread::spawn` 的那个闭包没有取任何参数:咱们没有在生成线程中,使用主线程中的任何数据。为在生成线程中使用主线程中的数据,那么生成线程的闭包就必须捕获其所需的值。下面清单 16-3 给出了在主线程中创建出一个矢量值,并在生成线程中用到这个矢量值的一种尝试。然而,正如即将看到的那样,这将尚不会运作。
@ -898,7 +898,7 @@ fn main() {
`Sync` 标识符表示实现 `Sync` 特质的类型,其被从多个线程引用是安全的。换句话说,任何类型 `T``&T` (即到 `T` 的不可变引用) 为 `Send` 的时,那么其即为 `Sync` 的,表示该引用可以安全地发送到另一线程。与 `Send` 类似,原生类型均为 `Sync` 的,且由全部都是 `Sync` 的类型所组成的类型,也都是 `Sync` 的。
灵巧指针 `Rc<T>` 因为其不是 `Send` 的同样原因,其也不是 `Sync` 的。`RefCell<T>` 类型(咱们曾在第 15 章讲过)以及相关的 `Cell<T>` 类型家族,都不是 `Sync` 的。`RefCell<T>` 在运行时所完成的借用检查实现,不是线程安全的。灵巧指针 `Mutex<T>``Sync` 的,并正如咱们在 [于多个线程间共用 `Mutex<T>`](#sharing-a-mutex-t-between-multiple-threads) 小节中看到的,其可被用于多个线程下共用访问。
灵巧指针 `Rc<T>` 因为其不是 `Send` 的同样原因,其也不是 `Sync` 的。`RefCell<T>` 类型(咱们曾在第 15 章讲过)以及相关的 `Cell<T>` 类型家族,都不是 `Sync` 的。`RefCell<T>` 在运行时所完成的借用检查实现,不是线程安全的。灵巧指针 `Mutex<T>``Sync` 的,并正如咱们在 [于多个线程间共用 `Mutex<T>`](#在多个线程间共用-mutext) 小节中看到的,其可被用于多个线程下共用访问。
### 手动实现 `Send``Sync` 是不安全的

View File

@ -59,7 +59,7 @@ match x {
`match` 表达式的一项要求,便是在表达式中那个值,所必须考虑到的全部可能性方面,需要 *详尽无遗exhaustive*。而一种确保咱们已经覆盖每种可能性的方式便是将一个捕获全部的模式a catchall pattern作为最后支臂比如一个匹配任意值的变量名称就绝不会失败而因此会覆盖每种其余的情形。
特别的模式 `_`,将匹配任何东西,但他绝不会绑定到某个变量,因此他通常被用在最后的匹配支臂中。在比如咱们打算忽略任何不予指定的值时,这种 `_` 模式就会是有用的。在本章稍后的 [“忽略模式中的值”](#ignoring-values-in-a-pattern) 小节中,咱们将更详细地讲到这种 `_` 模式。
特别的模式 `_`,将匹配任何东西,但他绝不会绑定到某个变量,因此他通常被用在最后的匹配支臂中。在比如咱们打算忽略任何不予指定的值时,这种 `_` 模式就会是有用的。在本章稍后的 [“忽略模式中的值”](#忽略模式中的某些值ignoring-values-in-a-pattern) 小节中,咱们将更详细地讲到这种 `_` 模式。
### 条件 `if let` 表达式
@ -216,7 +216,7 @@ For more information about this error, try `rustc --explain E0308`.
error: could not compile `while_let_demo` due to previous error
```
要修复该错误,咱们可以如同即将在 [于模式中忽略一些值](#ignoring-values-in-a-pattern) 小节中所看到的那样,使用 `_``..`,忽略那个元组中的一个或更多的值。当问题是咱们在模式中有太多变量时,那么办法就是通过移除一些变量构造出类型,从而变量数目便等于元组中元素的数目了。
要修复该错误,咱们可以如同即将在 [于模式中忽略一些值](#忽略模式中的某些值ignoring-values-in-a-pattern) 小节中所看到的那样,使用 `_``..`,忽略那个元组中的一个或更多的值。当问题是咱们在模式中有太多变量时,那么办法就是通过移除一些变量构造出类型,从而变量数目便等于元组中元素的数目了。
### 函数参数
@ -415,7 +415,7 @@ warning: `refutable_demo` (bin "refutable_demo") generated 1 warning
在这个 `match` 表达式完成是,他的作用域就结束了,而内层作用域的 `y` 也结束了。最后的 `println!` 会产生出 `最后x = Some(5), y = 10`
为创建出比较外层作用域中 `x``y` 值的一个 `match` 表达式而非引入一个遮蔽变量咱们将需要使用某种匹配卫兵条件a match guard conditional。稍后咱们将在 [“带有匹配保护的额外条件”](#extra-conditionals-with-match-guards) 小节,讨论到匹配保护问题。
为创建出比较外层作用域中 `x``y` 值的一个 `match` 表达式而非引入一个遮蔽变量咱们将需要使用某种匹配卫兵条件a match guard conditional。稍后咱们将在 [“带有匹配保护的额外条件”](#使用匹配卫兵的额外条件extra-conditionals-with-match-guards) 小节,讨论到匹配保护问题。

View File

@ -55,7 +55,7 @@ Rust 有着一个非安全的另外自我an unsafe alter ego的另一原
**Dereferencing a Raw Pointer**
在第 4 章的 [悬空引用](Ch04_Understanding_Ownership.md#dangling-references) 小节,咱们曾提到编译器会确保引用始终有效。不安全的 Rust 则有着与引用类似的, 叫做 *原始指针raw pointers* 的两种新类型。与引用一样,原始指针可以是不可变或可变的,并被相应地写作 `*const T``*mut T`。其中的星号 `*` 并非是解引用运算符;他是这种类型名字的一部分。在原始指针语境下,*不可变immutable* 意指该指针在被解引用之后,不能被直接赋值。
在第 4 章的 [悬空引用](Ch04_Understanding_Ownership.md#悬空引用dangling-references) 小节,咱们曾提到编译器会确保引用始终有效。不安全的 Rust 则有着与引用类似的, 叫做 *原始指针raw pointers* 的两种新类型。与引用一样,原始指针可以是不可变或可变的,并被相应地写作 `*const T``*mut T`。其中的星号 `*` 并非是解引用运算符;他是这种类型名字的一部分。在原始指针语境下,*不可变immutable* 意指该指针在被解引用之后,不能被直接赋值。
与引用及灵巧指针不同,原始指针有着以下特征:
@ -137,7 +137,7 @@ r2 为5
还要注意在清单 19-1 与 19-3 中,咱们创建的 `*const i32``*mut i32` 两个原始指针,都指向了同一内存地址,及 `num` 所存储之处。相反若咱们尝试创建到这个 `num` 的一个不可变与可变的引用,那么由于 Rust 的所有权规则在有任何不可变引用的同时,允许可变引用,该代码就不会被编译。有了原始指针,咱们就可以创建到同一内存地址的可变指针与不可变指针,而经由那个可变指针修改数据,就会潜在的造成数据竞争。所以请当心!
在全部的这些危险之下,咱们为何还要使用原始指针呢?一个主要的原因就是在与 C 代码交互时,正如将在下一小节,[”调用非安全函数或方法“](#calling-an-unsafe-function-or-method),中将看到的。另一中情况,便是在构建借用检查器不清楚的一些安全抽象时。咱们将介绍非安全函数,并在随后看看一个用到不安全代码的安全抽象。
在全部的这些危险之下,咱们为何还要使用原始指针呢?一个主要的原因就是在与 C 代码交互时,正如将在下一小节,[”调用非安全函数或方法“](#调用不安全函数或方法),中将看到的。另一中情况,便是在构建借用检查器不清楚的一些安全抽象时。咱们将介绍非安全函数,并在随后看看一个用到不安全代码的安全抽象。
### 调用不安全函数或方法
@ -261,7 +261,7 @@ fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
*清单 19-6`split_at_mut` 函数实现中使用不安全代码*
回顾第 4 章中的 [“切片类型”](Ch04_Understanding_Ownership.md#the-slice-type) 小节,切片即为到一些数据的指针,与切片的长度。咱们使用了 `len` 方法,来获取切片的长度,并使用 `as_mut_ptr` 方法来访问切片的原始指针。在这个示例中,由于咱们有着一个到一些 `i32` 值的可变切片,`as_mut_prr` 就会返回类型 `*mut i32` 的原始指针,其已被咱们存储在变量 `ptr` 中。
回顾第 4 章中的 [“切片类型”](Ch04_Understanding_Ownership.md#切片类型the-slice-type) 小节,切片即为到一些数据的指针,与切片的长度。咱们使用了 `len` 方法,来获取切片的长度,并使用 `as_mut_ptr` 方法来访问切片的原始指针。在这个示例中,由于咱们有着一个到一些 `i32` 值的可变切片,`as_mut_prr` 就会返回类型 `*mut i32` 的原始指针,其已被咱们存储在变量 `ptr` 中。
咱们保留了那个 `mid` 索引是在切片里的断言。随后咱们就到了那不安全代码处:`slice::from_raw_parts_mut` 函数会取一个原始指针及长度,并创建出一个切片。咱们使用这个函数,来创建自 `ptr` 开始,且长度为 `mid` 的一个切片。随后咱们以 `mid` 作为参数,调用 `ptr` 上的 `add` 方法,来得到于 `mid` 处开始的一个原始指针,而咱们创建出使用那个指针,且以 `mid` 之后项目数量为长度的一个切片。
@ -383,7 +383,7 @@ fn main() {
*清单 19-9定义并使用不可变静态变量*
静态变量与咱们曾在第三章中 [“变量与常量区别”](Ch03_Common_Programming_Concepts.md#constants) 小节讨论过的常量类似。静态变量的名字,依约定都是 `SCREAMING_SNAKE_CASE` 形式。静态变量只能存储有着 `'static` 声明周期的引用,这意味着 Rust 编译器可以计算出声明周期,而不要求咱们显式地对其加以注解。访问不可变的静态变量是安全的。
静态变量与咱们曾在第三章中 [“变量与常量区别”](Ch03_Common_Programming_Concepts.md#常量) 小节讨论过的常量类似。静态变量的名字,依约定都是 `SCREAMING_SNAKE_CASE` 形式。静态变量只能存储有着 `'static` 声明周期的引用,这意味着 Rust 编译器可以计算出声明周期,而不要求咱们显式地对其加以注解。访问不可变的静态变量是安全的。
常量与不可变静态变量的细微差别在于,静态变量里的值在内存中有着固定地址。用到该值就总是将访问同一数据。而另一方面的常量,则凡是在用到他们时,都是允许复制他们数据的。另一不同便是,静态变量可以是可变的。访问与修改可变静态变量是 *不安全的*。下面清单 19-10 给出了如何声明、访问及修改名为 `COUNT` 的可变静态变量方式。
@ -439,7 +439,7 @@ fn main() {}
通过使用 `unsafe impl`咱们就承诺咱们将坚守那些编译器无法验证的定数we'll uphold the invariants that the compiler can't verify。
作为示例,请回顾第 16 章中 [“`Sync` 与 `Send` 特质下的可扩展并发”](Ch16_Fearless_Concurrency.md#extensible-concurrency-with-the-sync-and-send-trait") 小节中,曾讨论过的 `Sync``Send` 两个标记性特质:在咱们的类型完全是由 `Send``Sync` 两种类型构成时,编译器就会自动实现这些特质。而在咱们实现某个包含了非 `Send``Sync` 的类型,比如原始指针,同时咱们打算将那个类型标记为 `Send``Sync` 时,咱们就必须使用 `unsafe`。Rust 无法验证咱们的类型坚守了其可被跨线程安全发送,或自多个线程安全访问的那些保证;因此,咱们就需要手动完成这些检查,并以 `unsafe` 照这样加以表明。
作为示例,请回顾第 16 章中 [“`Sync` 与 `Send` 特质下的可扩展并发”](Ch16_Fearless_Concurrency.md#sync-与-send-两个特质下的可扩展并发) 小节中,曾讨论过的 `Sync``Send` 两个标记性特质:在咱们的类型完全是由 `Send``Sync` 两种类型构成时,编译器就会自动实现这些特质。而在咱们实现某个包含了非 `Send``Sync` 的类型,比如原始指针,同时咱们打算将那个类型标记为 `Send``Sync` 时,咱们就必须使用 `unsafe`。Rust 无法验证咱们的类型坚守了其可被跨线程安全发送,或自多个线程安全访问的那些保证;因此,咱们就需要手动完成这些检查,并以 `unsafe` 照这样加以表明。
### 访问联合体的字段
@ -457,7 +457,7 @@ fn main() {}
## 高级特质
在第 10 章 [“特质:定义共用行为”](Ch10_Generic_Types_Traits_and_Lifetimes.md#trait-defining-shared-behavior) 小节中,咱们曾首先涉及到特质,但咱们不曾讨论更为高级的那些细节。现在咱们对 Rust 有了更多了解咱们就可以深入本质get into the nitty-gritty。
在第 10 章 [“特质:定义共用行为”](Ch10_Generic_Types_Traits_and_Lifetimes.md#特质定义共用行为) 小节中,咱们曾首先涉及到特质,但咱们不曾讨论更为高级的那些细节。现在咱们对 Rust 有了更多了解咱们就可以深入本质get into the nitty-gritty。
### 使用关联类型指定出特质定义中的一些占位性类型
@ -576,7 +576,7 @@ trait Add<Rhs=Self> {
当咱们为 `Point` 实现 `Add` 时,由于咱们打算把两个 `Point` 实例相加,因此而使用了 `Rhs` 的默认值。接下来看看,其中咱们打算定制那个 `Rhs` 而非使用其默认值的一个 `Add` 实现示例。
咱们有着两个结构体,`Millimeters` 与 `Meters`保存着不同单位的一些值。这种将某个既有类型封装在另一结构体的瘦封装thin wrapping就叫做 *新类型模式newtype pattern*,在后面的 [“使用新型模式在外部类型上实现外部特质”](#using-the-newtype-pattern-to-implement-external-traits-on-external-types) 小节,咱们会对其进行更深入讨论。咱们打算把毫米值与以米计数的值相加,并要让 `Add` 的实现,正确完成单位转换。咱们可在将 `Meters` 作为 `Rhs` 下,对 `Millimeters` 实现 `Add`,如下清单 19-15 中所示。
咱们有着两个结构体,`Millimeters` 与 `Meters`保存着不同单位的一些值。这种将某个既有类型封装在另一结构体的瘦封装thin wrapping就叫做 *新类型模式newtype pattern*,在后面的 [“使用新型模式在外部类型上实现外部特质”](#使用新型模式在外层类型上实现外层的特质) 小节,咱们会对其进行更深入讨论。咱们打算把毫米值与以米计数的值相加,并要让 `Add` 的实现,正确完成单位转换。咱们可在将 `Meters` 作为 `Rhs` 下,对 `Millimeters` 实现 `Add`,如下清单 19-15 中所示。
```rust
@ -896,7 +896,7 @@ impl fmt::Display for Point {
**Using the Newtype Pattern to Implement External Traits on External Types**
第 10 章中的 [“在类型上实现特质”](Ch10_Generic_Types_Traits_and_Lifetimes.md#implmenting-a-trait-on-a-type) 小节咱们曾提到指明只有当特质或类型二者之一属于代码本地的时咱们才被允许在类型上实现特质的孤儿规则the orphan rule。而使用涉及到在元组结构体中创建出一个新类型的 *新型模式newtype pattern*,那么绕过这种限制便是可行的了。(咱们曾在第 5 章的 [“使用不带命名字段的元组结构体来创建不同类型”](Ch05_Using_Structs_to_Structure_Related_Data.md#using-tuple-structs-without-named-fields-to-create-different-types") 小节谈到过元组结构体这种元组结构体讲有一个字段且将是围绕咱们要实现某个特质的类型的一个瘦封装a thin wrapper。随后这个封装类型便是咱们代码箱的本地类型了而咱们就可以在这个封装上实现那个特质了。所谓 *新型newtype*,是源自 Haskell 编程语言的一个术语。使用这种模式没有运行时性能代码,同时那个封装类型在编译时会被略去。
第 10 章中的 [“在类型上实现特质”](Ch10_Generic_Types_Traits_and_Lifetimes.md#在类型上实现某个特质) 小节咱们曾提到指明只有当特质或类型二者之一属于代码本地的时咱们才被允许在类型上实现特质的孤儿规则the orphan rule。而使用涉及到在元组结构体中创建出一个新类型的 *新型模式newtype pattern*,那么绕过这种限制便是可行的了。(咱们曾在第 5 章的 [“使用不带命名字段的元组结构体来创建不同类型”](Ch05_Using_Structs_to_Structure_Related_Data.md#使用不带命名字段的元组结构体来创建不同类型) 小节谈到过元组结构体这种元组结构体讲有一个字段且将是围绕咱们要实现某个特质的类型的一个瘦封装a thin wrapper。随后这个封装类型便是咱们代码箱的本地类型了而咱们就可以在这个封装上实现那个特质了。所谓 *新型newtype*,是源自 Haskell 编程语言的一个术语。使用这种模式没有运行时性能代码,同时那个封装类型在编译时会被略去。
作为一个示例,就说咱们打算在 `Vec<T>` 上实现 `Display`,而由于 `Display` 特质与 `Vec<T>` 类型,均被定义在咱们代码箱外部,因此孤儿规则会阻止咱们直接这样做。咱们可以构造一个保存着 `Vec<T>` 类型实例的 `Wrapper`;随后咱们就可以在 `Wrapper` 上实现 `Display`,并使用那个 `Vec<T>` 值,如下清单 19-23 中所示。
@ -924,7 +924,7 @@ fn main() {
由于 `Wrapper` 是个元组结构体,且 `Vec<T>` 是该元组中位于索引 `0` 处的项目,因此其中 `Display` 的实现,便使用了 `self.0` 来方法那个内部的 `Vec<T>`。随后咱们就可以在 `Wrapper` 上使用 `Display` 的功能了。
使用这种技巧的缺点,则是那个 `Wrapper` 是个新的类型,因此其没有他所保存值的那些方法。咱们讲必须直接在 `Wrapper` 上,实现 `Vec<T>` 的全部方法,即委托给 `self.0` 的那些方法,这就会允许咱们将 `Wrapper` 完全当作 `Vec<T>` 那样对待了。而若咱们想要这个新的类型,有着那个内部类型所有的全部方法,那么在 `Wrapper` 上实现 `Deref` 特质(曾在第 15 章的 [“运用 `Deref` 特质将灵巧指针像常规引用那样对待”](Ch15_Smart_Pointers.md#treating-smart-pointers-like-regular-references-with-deref-trait) 小节讨论过),来返回那个内部类型,将是一种办法。而若咱们不打算 `Wrapper` 类型有着内部类型的所有方法 -- 比如,为限制 `Wrapper` 的行为 -- 咱们就必须手动实现仅咱们想要的那些方法了。
使用这种技巧的缺点,则是那个 `Wrapper` 是个新的类型,因此其没有他所保存值的那些方法。咱们讲必须直接在 `Wrapper` 上,实现 `Vec<T>` 的全部方法,即委托给 `self.0` 的那些方法,这就会允许咱们将 `Wrapper` 完全当作 `Vec<T>` 那样对待了。而若咱们想要这个新的类型,有着那个内部类型所有的全部方法,那么在 `Wrapper` 上实现 `Deref` 特质(曾在第 15 章的 [“运用 `Deref` 特质将灵巧指针像常规引用那样对待”](Ch15_Smart_Pointers.md#通过实现-deref-特质而像引用那样对待某个类型) 小节讨论过),来返回那个内部类型,将是一种办法。而若咱们不打算 `Wrapper` 类型有着内部类型的所有方法 -- 比如,为限制 `Wrapper` 的行为 -- 咱们就必须手动实现仅咱们想要的那些方法了。
即使不牵涉到特质,这种新型模式也是有用的。接下来就要转换一下视角,而看看与 Rust 的类型系统交互的一些高级方式。
@ -941,14 +941,14 @@ Rust 的类型系统有着一些到目前为止咱们曾提到过但尚未讨论
**Using the Newtype Pattern for Type Safety and Abstraction**
> **注意**:此小节假定你已读过早先的 [“使用新型模式来再外层类型上实现外层的特质”](#using-the-newtype-pattern-to-implement-external-traits-on-external-types") 小节。
> **注意**:此小节假定你已读过早先的 [“使用新型模式来再外层类型上实现外层的特质”](#使用新型模式在外层类型上实现外层的特质") 小节。
对于那些超出到目前为止咱们曾讨论过的任务,包括静态强制要求值绝不会混淆,以及表明某个值的单位等等,新型模式同样是有用的。在清单 19-15 中,咱们就曾看到一个使用新型,表明单位的一个示例:回顾到 `Millimeters``Meters` 两个结构体,都曾将 `u32` 值封装在新型中。而若咱们编写了带有一个类型 `Millimeters` 参数的函数,那么咱们就无法编译某个偶然尝试以类型 `Meters` 或普通 `u32` 的值,调用那个函数的程序。
咱们还可以使用新型模式,来抽象出某个类型的一些实现细节:新的类型可暴露处不同意私有内部类型 API 的一个公开 API。
新类型还可以隐藏内部实现。比如,咱们可提供一个 `People` 类型,来封装一个存储着某人与其名字关联的 ID 的 `HashMap<i32, String>`。使用 `People` 的代码,只需与咱们提供的公开 API比如某个将名字字符串添加到 `People` 集合的方法交互;那些代码将不需要知悉咱们在内部分配了`i32` 的 ID 给那些名字。新型模式是达成,咱们曾在第 17 章讨论过的 [“隐藏实现细节的封装”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#encapsulation-that-hides-implementation-details") 的一种轻量方式。
新类型还可以隐藏内部实现。比如,咱们可提供一个 `People` 类型,来封装一个存储着某人与其名字关联的 ID 的 `HashMap<i32, String>`。使用 `People` 的代码,只需与咱们提供的公开 API比如某个将名字字符串添加到 `People` 集合的方法交互;那些代码将不需要知悉咱们在内部分配了`i32` 的 ID 给那些名字。新型模式是达成,咱们曾在第 17 章讨论过的 [“隐藏实现细节的封装”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#隐藏了实现细节的封装) 的一种轻量方式。
### 使用类型别名创建类型同义词
@ -1082,7 +1082,7 @@ fn bar() -> ! {
*清单 19-26有着一个以 `continue` 结束支臂的 `match` 表达式*
那个时候,咱们跳过了此代码的一些细节。而在第 6 章中的 [“`match` 控制流运算符”](Ch06_Enums_and_Pattern_Matching.md#the-match-control-flow-construct) 小节,咱们曾讨论了 `match` 支臂必须全部返回同一类型。那么,比如说,下面的代码就不会工作:
那个时候,咱们跳过了此代码的一些细节。而在第 6 章中的 [“`match` 控制流运算符”](Ch06_Enums_and_Pattern_Matching.md#match-控制流结构) 小节,咱们曾讨论了 `match` 支臂必须全部返回同一类型。那么,比如说,下面的代码就不会工作:
```rust
@ -1141,9 +1141,9 @@ Rust 需要知道其类型的确切情况,比如给某种特定类型值分配
Rust 需要清楚,要给特定类型的任何值分配多少内存,且某种类型的所有值,都必须使用同样数量的内存。若 Rust 运行咱们编写此代码,那么这两个 `str` 值就将需要占据同样数量的内存空间。但他们有着不同长度:`s1` 需要 15 字节的存储,而 `s2` 需要 `24` 字节。这就是为何创建保存动态大小类型值的变量不可行的原因。
那么咱们要怎么做呢?在这种情况下,咱们就已经知道答案了:咱们要令到 `s1``s2` 的类型为 `&str` 而非 `str`。从第 4 章的 [“字符串切片”](Ch04_Understanding_Ownership.md#string-slices) 小节,回顾到切片数据结构,只会存储其开始位置和切片的长度。因此尽管 `&T` 是存储了 `T` 所处内存地址的单个值,而一个 `&str` 则是 *两个* 值:`str` 的地址与其长度。如此,咱们就知道某个 `&str` 在编译时的大小了:其为 `uszie` 长度的两倍。那便是,咱们总是清楚 `&str` 的大小,而不管他所指向的字符串有多长。一般来说,这就是 Rust 中动态大小类型被运用的方式:他们有着存储了动态信息大小的额外的一点元数据。动态大小类型的黄金法则,就是咱们必须始终把那些动态大小类型的值,放置某种指针之后。
那么咱们要怎么做呢?在这种情况下,咱们就已经知道答案了:咱们要令到 `s1``s2` 的类型为 `&str` 而非 `str`。从第 4 章的 [“字符串切片”](Ch04_Understanding_Ownership.md#字符串切片) 小节,回顾到切片数据结构,只会存储其开始位置和切片的长度。因此尽管 `&T` 是存储了 `T` 所处内存地址的单个值,而一个 `&str` 则是 *两个* 值:`str` 的地址与其长度。如此,咱们就知道某个 `&str` 在编译时的大小了:其为 `uszie` 长度的两倍。那便是,咱们总是清楚 `&str` 的大小,而不管他所指向的字符串有多长。一般来说,这就是 Rust 中动态大小类型被运用的方式:他们有着存储了动态信息大小的额外的一点元数据。动态大小类型的黄金法则,就是咱们必须始终把那些动态大小类型的值,放置某种指针之后。
咱们可将 `str` 与所有类别的指针结合:比如,`Box<str>` 或 `Rc<str>`。事实上,之前咱们就已经见到过这样的,只不过是在一种不同的动态大小类型下:那便是特质。每个特质都是咱们可以通过使用特质名字而加以引用的动态大小类型。在第 17 章中的 [“使用允许不同类型值的特质对象”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#using-trait-objects-that-allow-for-values-of-different-types) 小节,咱们曾提到为了将特质用作特质对象,咱们就必须将其放在指针之后,比如 `&dyn Trait``Box<dyn Trait>` `Rc<dyn Trait>` 也应生效)。
咱们可将 `str` 与所有类别的指针结合:比如,`Box<str>` 或 `Rc<str>`。事实上,之前咱们就已经见到过这样的,只不过是在一种不同的动态大小类型下:那便是特质。每个特质都是咱们可以通过使用特质名字而加以引用的动态大小类型。在第 17 章中的 [“使用允许不同类型值的特质对象”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#使用允许不同类型值的特质对象) 小节,咱们曾提到为了将特质用作特质对象,咱们就必须将其放在指针之后,比如 `&dyn Trait``Box<dyn Trait>` `Rc<dyn Trait>` 也应生效)。
为处理 DSTs 相关问题Rust 提供了 `Sized` 特质来判断在编译时某个类型的大小是否已知。在运行时大小已知的全部物件都已自动实现了这个特质。此外Rust 会隐式地将 `Sized` 上的边界,添加到每个泛型函数。那就是说,像下面的一个泛型函数:
@ -1246,9 +1246,9 @@ fn main() {
list_of_numbers.iter().map(ToString::to_string).collect();
```
请注意由于有着多个可用的名为 `to_string` 函数,因此咱们就必须使用早先在 [“高级特质”](#advanced-traites) 小节中讲到的完全合格语法。这里咱们使用了那个标准库已对任何实现了 `Display` 类型,实现过了的 `ToString` 特质中的 `to_string` 函数。
请注意由于有着多个可用的名为 `to_string` 函数,因此咱们就必须使用早先在 [“高级特质”](#高级特质) 小节中讲到的完全合格语法。这里咱们使用了那个标准库已对任何实现了 `Display` 类型,实现过了的 `ToString` 特质中的 `to_string` 函数。
自第 6 章 [“枚举取值”](Ch06_Enums_and_Pattern_Matching.md#enum-values) 小节,回顾咱们所定义的各个枚举变种名字,也会成为一个初始化函数。咱们可以将这些初始化函数,作为实现了那些闭包特质的函数指针使用,这就意味着咱们可以把这些初始化函数,指定为取闭包的方法的参数,像下面这样:
自第 6 章 [“枚举取值”](Ch06_Enums_and_Pattern_Matching.md#枚举取值) 小节,回顾咱们所定义的各个枚举变种名字,也会成为一个初始化函数。咱们可以将这些初始化函数,作为实现了那些闭包特质的函数指针使用,这就意味着咱们可以把这些初始化函数,指定为取闭包的方法的参数,像下面这样:
```rust
enum Status {
@ -1307,7 +1307,7 @@ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
}
```
这段代码可以很好地编译。有关特质对象的更多内容,请参考第 17 章中的 [“使用特质对象实现不同类型值”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#using-trait-objects-that-allow-for-values-of-different-types) 小节。
这段代码可以很好地编译。有关特质对象的更多内容,请参考第 17 章中的 [“使用特质对象实现不同类型值”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#使用允许不同类型值的特质对象) 小节。
接下来,咱们就要看看宏了!
@ -1650,7 +1650,7 @@ pub fn route(attr: TokenStream, item: TokenStream) -> TokenSteam {
**Function-link macros**
类函数宏定义了看起来像函数调用的宏。与 `macro_rules!` 宏类似,他们比函数更为灵活;比如,他们就可取未知数目的参数。然而,`macro_rules!` 宏只能使用咱们早先在 [用于通用元编程的带有 `macro_rules!` 的声明式宏](#declarative-macros-with-macro_rules-for-general-metaprogramming") 小节,曾讨论过的 match-like 语法。而类函数宏,则会取一个 `TokenStream` 参数,而这些宏的定义,就会使用 Rust 代码,如同另外两种程序性宏所做的那样,对那个 `TokenStream` 加以操纵。作为类函数宏的一个例子,便是将如下面调用的一个 `sql!` 宏:
类函数宏定义了看起来像函数调用的宏。与 `macro_rules!` 宏类似,他们比函数更为灵活;比如,他们就可取未知数目的参数。然而,`macro_rules!` 宏只能使用咱们早先在 [用于通用元编程的带有 `macro_rules!` 的声明式宏](#用于通用元编程的带有-macro_rules-的声明式宏) 小节,曾讨论过的 match-like 语法。而类函数宏,则会取一个 `TokenStream` 参数,而这些宏的定义,就会使用 Rust 代码,如同另外两种程序性宏所做的那样,对那个 `TokenStream` 加以操纵。作为类函数宏的一个例子,便是将如下面调用的一个 `sql!` 宏:
```rust
let sql = sql! (SELECT * FROM posts WHERE id=1);

View File

@ -665,7 +665,7 @@ impl ThreadPool {
}
```
由于咱们清楚一个负的线程数目不会有任何意义,因此咱们选择了 `usize` 作为那个 `size` 参数的类型。咱们还知道咱们将使用这个 `4` 作为线程集合中原始的个数,那即使这个 `usize` 类型的目的所在,正如第三章的 [整数类型](Ch03_Common_Programming_Concepts.md#integer-types) 小节中曾讨论过的。
由于咱们清楚一个负的线程数目不会有任何意义,因此咱们选择了 `usize` 作为那个 `size` 参数的类型。咱们还知道咱们将使用这个 `4` 作为线程集合中原始的个数,那即使这个 `usize` 类型的目的所在,正如第三章的 [整数类型](Ch03_Common_Programming_Concepts.md#整形) 小节中曾讨论过的。
下面来再次检查:
@ -682,9 +682,9 @@ For more information about this error, try `rustc --explain E0599`.
error: could not compile `hello` due to previous error
```
现在的报错之所以出现,是因为在 `ThreadPool` 上咱们没有一个 `execute` 方法。回顾 ["创建有限数目的线程"](#creating-a-finite-number-of-threads) 小节到,咱们已决定咱们的线程池,应有一个类似与 `thread::spawn` 的接口。此外,咱们将实现这个 `execute` 函数,如此其便会取那个给到他的闭包,并将其交给线程池中的某个空闲进程运行。
现在的报错之所以出现,是因为在 `ThreadPool` 上咱们没有一个 `execute` 方法。回顾 ["创建有限数目的线程"](#创建有限数目的线程) 小节到,咱们已决定咱们的线程池,应有一个类似与 `thread::spawn` 的接口。此外,咱们将实现这个 `execute` 函数,如此其便会取那个给到他的闭包,并将其交给线程池中的某个空闲进程运行。
咱们将在 `ThreadPool` 上定义这个 `execute` 方法,来取一个闭包作为参数。回顾第 13 章中 [“将捕获值迁移出闭包与 `Fn` 特质”](Ch13_Functional_Language_Features_Iterators_and_Closures.md#moving-captured-values-out-of-closures-and-the-Fn-traits) 到咱们可以三种不同特质,将闭包取作参数:`Fn`、`FnMut` 与 `FnOnce`。咱们需要确定出这里要使用何种类别的闭包。咱们清楚咱们将以完成一些类似于标准库的 `thread::spawn` 实现类似的东西结束,因此咱们就可以看看 `thread::spawn` 的签名在其参数上有些什么。文档给出咱们下面的东西:
咱们将在 `ThreadPool` 上定义这个 `execute` 方法,来取一个闭包作为参数。回顾第 13 章中 [“将捕获值迁移出闭包与 `Fn` 特质”](Ch13_Functional_Language_Features_Iterators_and_Closures.md#将捕获到的值迁移出闭包与-fn-特质) 到咱们可以三种不同特质,将闭包取作参数:`Fn`、`FnMut` 与 `FnOnce`。咱们需要确定出这里要使用何种类别的闭包。咱们清楚咱们将以完成一些类似于标准库的 `thread::spawn` 实现类似的东西结束,因此咱们就可以看看 `thread::spawn` 的签名在其参数上有些什么。文档给出咱们下面的东西:
```rust
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
@ -1057,7 +1057,7 @@ impl Worker {
**Implementing the `execute` method**
咱们来最终实现那个 `ThreadPool` 上的 `execute` 方法。咱们还将把 `Job` 从结构体,修改为保存着 `execute` 接收到闭包类型的特质对象的类型别名。正如第 19 章 [“使用类型别名创建类型义词”](Ch19_Advanced_Features.md#creating-type-synonyms-with-type-aliases) 小节中曾讨论过的,类型别名实现了为易于使用而将长类型构造缩短。请看看下面清单 20-19.
咱们来最终实现那个 `ThreadPool` 上的 `execute` 方法。咱们还将把 `Job` 从结构体,修改为保存着 `execute` 接收到闭包类型的特质对象的类型别名。正如第 19 章 [“使用类型别名创建类型义词”](Ch19_Advanced_Features.md#使用类型别名创建类型同义词) 小节中曾讨论过的,类型别名实现了为易于使用而将长类型构造缩短。请看看下面清单 20-19.
文件名:`src/lib.rs`