Merge pull request #4 from KaiserY/master

syncing
pull/139/head
Yu Sun 7 years ago committed by GitHub
commit 828c141282
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,7 +6,7 @@
欢迎阅读 “Rust 程序设计语言”,一本介绍 Rust 的书。Rust 是一门着眼于安全、速度和并发的编程语言。其程序设计兼顾底层语言的性能与控制,并不失高级语言强大的抽象能力。其特性适合那些有类 C 语言经验,正在寻找更安全的替代品的开发者;同样适合有着类 Python 语言背景,寻求在不牺牲表现力的前提下,编写更高性能代码的开发者。
Rust 编译时执行绝大部分的安全检查和内存管理决策,对运行时性能的影响微不足道。这使其在其他语言不擅长的应用场景中得以大显身手:可预测时间和空间需求的程序,嵌入到其他语言中,以及编写如设备驱动和操作系统这样的底层代码。Rust 也很擅长 web 程序:它驱动着 Rust 包登记网站package
Rust 编译时执行绝大部分的安全检查和内存管理决策对运行时性能的影响微不足道。这使其在其他语言不擅长的应用场景中得以大显身手可预测时间和空间需求的程序嵌入到其他语言中以及编写如设备驱动和操作系统这样的底层代码。Rust 也很擅长 web 程序:它驱动着 Rust 包登记网站package
registry site[crates.io]!我们由衷期待**你**使用 Rust 进行创作。
[crates.io]: https://crates.io/
@ -22,4 +22,4 @@ registry site[crates.io]!我们由衷期待**你**使用 Rust 进行创
> 译者注:译本的 [GitHub 仓库][trpl-zh-cn],同样欢迎 Issue 和 PR :)
[trpl-zh-cn]: https://github.com/KaiserY/trpl-zh-cn
[trpl-zh-cn]: https://github.com/KaiserY/trpl-zh-cn

@ -85,7 +85,7 @@ fn main() {
这行代码完成这个小程序的所有工作:在屏幕上打印文本。这里有很多细节需要注意。首先 Rust 使用 4 个空格的缩进风格,而不是 1 个制表符tab
第二个重要的部分是 `println!()`。这称为 Rust **宏**Rust 元编程metaprogramming的关键所在。如果是调用函数则应看起来像这样`println`(没有`!`)。我们将在附录 E 中更加详细的讨论宏,现在你只需记住,当看到符号 `!` 的时候,调用的是宏而不是普通函数。
第二个重要的部分是 `println!()`。这称为 Rust **宏**Rust 元编程metaprogramming的关键所在。如果是调用函数则应看起来像这样`println`(没有`!`)。我们将在附录 E 中更加详细的讨论宏,现在你只需记住,当看到符号 `!` 的时候,调用的是宏而不是普通函数。
接下来,`"Hello, world!"` 是一个 **字符串**。我们把这个字符串作为一个参数传递给 `println!`,它负责在屏幕上打印这个字符串。轻松加愉快!(⊙o⊙)

@ -89,7 +89,7 @@ const MAX_POINTS: u32 = 100_000;
常量在整个程序生命周期中都有效,位于它声明的作用域之中。这使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
将用于整个程序的硬编码的值声明为常量对后来的维护者了解值的意义很帮助。它也能将硬编码的值汇总一处,为将来可能的修改提供方便。
将用于整个程序的硬编码的值声明为常量对后来的维护者了解值的意义很帮助。它也能将硬编码的值汇总一处,为将来可能的修改提供方便。
### 隐藏Shadowing

@ -207,7 +207,7 @@ fn main() {
}
```
数组需要在栈stack而不是在堆heap上为数据分配空间时第四章将讨论栈与堆的更多内容或者是想要确保总是有固定数量的元素时十分有用。虽然它并不如 vector 类型那么灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector第八章会详细讨论 vector。
数组需要在栈stack而不是在堆heap上为数据分配空间时第四章将讨论栈与堆的更多内容或者是想要确保总是有固定数量的元素时十分有用。虽然它并不如 vector 类型那么灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector第八章会详细讨论 vector。
一个你可能想要使用数组而不是 vector 的例子是当程序需要知道一年中月份的名字时。程序不大可能会去增加或减少月份,这时你可以使用数组因为我们知道它总是含有 12 个元素:

@ -96,7 +96,7 @@ The value of x is: 5
The value of y is: 6
```
因为我们使用 `5` 作为 `x` 的值和 `6` 作为 `y` 的值来调用函数,这两个字符串和他们的值被打印出来。
因为我们使用 `5` 作为 `x` 的值和 `6` 作为 `y` 的值来调用函数,这两个字符串和他们的值被相应打印出来。
### 函数体
@ -104,7 +104,7 @@ The value of y is: 6
### 语句与表达式
我们已经用过语句与表达式了。**语句***Statements*)是执行一些操作但不返回值的指令。表达式(*Expressions*)计算并产生一个值。让我们看一些例子:
我们已经用过语句与表达式了。**语句***Statements*)是执行一些操作但不返回值的指令。表达式(*Expressions*)计算并产生一个值。让我们看一些例子:
使用 `let` 关键字创建变量并绑定一个值是一个语句。在列表 3-3 中,`let y = 6;` 是一个语句:
@ -146,7 +146,7 @@ error: expected expression, found statement (`let`)
`let y = 6` 语句并不返回值,所以并没有 `x` 可以绑定的值。这与其他语言不同,例如 C 和 Ruby他们的赋值语句返回所赋的值。在这些语言中可以这么写 `x = y = 6` 这样 `x``y` 的值都是 `6`;这在 Rust 中可不行。
表达式计算出一些值,而且他们组成了其余大部分你将会编写的 Rust 代码。考虑一个简单的数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在列表 3-3 中有这个语句 `let y = 6;``6` 是一个表达式它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建作用域的大括号(代码块),`{}`,也是一个表达式,例如:
表达式计算出一些值,而且他们组成了其余大部分你将会编写的 Rust 代码。考虑一个简单的数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在列表 3-3 中有这个语句 `let y = 6;``6` 是一个表达式它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建作用域的大括号(代码块),`{}`,也是一个表达式,例如:
<span class="filename">文件名: src/main.rs</span>

@ -54,7 +54,7 @@ $ cargo run
condition was false
```
另外值得注意的是代码中的条件 **必须** 是 `bool`。如果看看条件不是 `bool` 值时会发生什么,尝试运行如下代码:
另外值得注意的是代码中的条件 **必须** 是 `bool`。如果看看条件不是 `bool` 值时会发生什么,尝试运行如下代码:
<span class="filename">文件名: src/main.rs</span>
@ -202,7 +202,7 @@ error[E0308]: if and else have incompatible types
### 使用循环重复执行
多次执行同一段代码是很常用的。为了这个功能Rust 提供了多种 **循环***loops*)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们创建一个叫做 *loops* 的新项目。
多次执行同一段代码是很常用的。为了这个功能Rust 提供了多种 **循环***loops*)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们创建一个叫做 *loops* 的新项目。
Rust 有三种循环类型:`loop`、`while` 和 `for`。让我们每一个都试试。

@ -171,7 +171,7 @@ let s2 = s1;
之前,我们提到过当变量离开作用域后 Rust 自动调用 `drop` 函数并清理变量的堆内存。不过图 4-4 展示了两个数据指针指向了同一位置。这就有了一个问题:当 `s2``s1` 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 *double free* 的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存Rust 则认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生么:
为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。与其尝试拷贝被分配的内存Rust 则认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生么:
```rust,ignore
let s1 = String::from("hello");
@ -195,7 +195,7 @@ error[E0382]: use of moved value: `s1`
which does not implement the `Copy` trait
```
如果你在其他语言中听说过术语 “浅拷贝”“shallow copy”和 “深拷贝”“deep copy”那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效化了,这个操作被称为 **移动***move*),而不是浅拷贝。上面的例子可以解读为 `s1`**移动** 到了 `s2` 中。那么具体发生了什么如图 4-6 所示。
如果你在其他语言中听说过术语 “浅拷贝”“shallow copy”和 “深拷贝”“deep copy”那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效化了,这个操作被称为 **移动***move*),而不是浅拷贝。上面的例子可以解读为 `s1`**移动** 到了 `s2` 中。那么具体发生了什么如图 4-6 所示。
<img alt="s1 moved to s2" src="img/trpl04-04.svg" class="center" style="width: 50%;" />
@ -321,7 +321,7 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
变量的所有权总是遵循相同的模式:将值赋值给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取并接着返回所有权可能有些冗余。如果我们想要函数使用一个值但不获取所有权怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,另外我们也可能想要返回函数体产生的任何(不止一个)数据。
在每一个函数中都获取并接着返回所有权可能有些冗余。如果我们想要函数使用一个值但不获取所有权怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,另外我们也可能想要返回函数体产生的任何(不止一个)数据。
可以使用元组来返回多个值,像这样:

@ -4,9 +4,9 @@
> <br>
> commit 56352c28cf3fe0402fa5a7cba73890e314d720eb
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
为了定义结构体,通过 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,他们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,他们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
```rust
struct User {
@ -178,7 +178,7 @@ let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
```
注意 `black``origin` 变量不同的类型,因为他们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。
注意 `black``origin` 变量不同的类型,因为他们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。
### 没有任何字段的类单元结构体

@ -71,7 +71,7 @@ fn area(dimensions: (u32, u32)) -> u32 {
### 使用结构体重构:增加更多意义
现在引入结构体的时候了。我们可以将元组转换为一个有整体名称而且每个部分也有对应名字的数据类型,如示例 5-10 所示:
现在引入结构体的时候了。我们可以将元组转换为一个有整体名称而且每个部分也有对应名字的数据类型,如示例 5-10 所示:
<span class="filename">文件名: src/main.rs</span>
@ -174,7 +174,7 @@ fn main() {
<span class="caption">示例 5-12增加注解来导出 `Debug` trait </span>
此时此刻运行程序,运行这个程序,不会有任何错误并会出现如下输出:
现在我们再运行这个程序时,不会有任何错误并会出现如下输出:
```text
rect1 is Rectangle { length: 50, width: 30 }

@ -170,7 +170,7 @@ println!("{:?}", map);
vector、字符串和哈希 map 会在你的程序需要储存、访问和修改数据时帮助你。这里有一些你应该能够解决的练习问题:
* 给定一系列数字,使用 vector 并返回这个列表的平均数mean, average、中位数排列数组后位于中间的值和众数mode出现次数最多的值这里哈希函数会很有帮助
* 将字符串转换为 Pig Latin也就是每一个单词的第一个辅音字母被移动到单词的结尾并增加 “ay”所以 “first” 会变成 “first-fay”。元音字母开头的单词则在结尾增加 “hay”“apple” 会变成 “apple-hay”。牢记 UTF-8 编码!
* 将字符串转换为 Pig Latin也就是每一个单词的第一个辅音字母被移动到单词的结尾并增加 “ay”所以 “first” 会变成 “irst-fay”。元音字母开头的单词则在结尾增加 “hay”“apple” 会变成 “apple-hay”。牢记 UTF-8 编码!
* 使用哈希 map 和 vector创建一个文本接口来允许用户向公司的部门中增加员工的名字。例如“Add Sally to Engineering” 或 “Add Amir to Sales”。接着让用户获取一个部门的所有员工的列表或者公司每个部门的所有员工按照字母顺排序的列表。
标准库 API 文档中描述的这些类型的方法将有助于你进行这些练习!

@ -57,7 +57,7 @@ fn main() {
这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。`[]` 应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。
这种情况下其他像 C 这样语言会尝直接提供所要求的值,即便这可能不是你期望的:你会得到任何应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为 **缓冲区溢出***buffer overread*),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。
这种情况下其他像 C 这样语言会尝直接提供所要求的值,即便这可能不是你期望的:你会得到任何应 vector 中这个元素的内存位置的值,甚至是这些内存并不属于 vector 的情况。这被称为 **缓冲区溢出***buffer overread*),并可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取储存在数组后面不被允许的数据。
为了使程序远离这类漏洞如果尝试读取一个索引不存在的元素Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下:
@ -122,4 +122,4 @@ stack backtrace:
如果你不希望我们的程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在上面的例子中我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
本章的后面会再次回到 `panic!` 并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
本章的后面会再次回到 `panic!` 并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。

@ -309,7 +309,7 @@ fn main() {
### 泛型代码的性能
在阅读本部分的内容的同时你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是Rust 实现泛型泛型的方式意味着你的代码使用泛型类型参数相比指定具体类型并没有任何速度上的损失。
在阅读本部分的内容的同时你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是Rust 实现泛型的方式意味着你的代码使用泛型类型参数相比指定具体类型并没有任何速度上的损失。
Rust 通过在编译时进行泛型代码的 **单态化***monomorphization*)来保证效率。单态化是一个将泛型代码转变为实际放入的具体类型的特定代码的过程。
@ -345,4 +345,4 @@ fn main() {
}
```
我们可以使用泛型来编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。
我们可以使用泛型来编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。

@ -70,7 +70,7 @@ impl Summarizable for Tweet {
<span class="caption">示例 10-13`NewsArticle``Tweet` 类型上实现 `Summarizable` trait</span>
在类型上实现 trait 类似实现与 trait 无关的方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。
在类型上实现 trait 类似实现与 trait 无关的方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。
一旦实现了 trait我们就可以用与 `NewsArticle``Tweet` 实例的非 trait 方法一样的方式调用 trait 方法了:

@ -214,7 +214,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
当具体的引用被传递给 `longest` 时,被 `'a` 所替代的具体生命周期是 `x` 的作用域与 `y` 的作用域相重叠的那一部分。因为作用域总是嵌套的,所以换一种说法就是泛型生命周期 `'a` 的具体生命周期等同于 `x``y` 的生命周期中较小的那一个。因为我们用相同的生命周期参数标注了返回的引用值,所以返回的引用值就能保证在 `x``y` 中较短的那个生命周期结束之前保持有效。
让我们如何通过传递拥有不同具体生命周期的引用来观察他们是如何限制 `longest` 函数的使用。示例 10-24 是一个应该在任何编程语言中都很直观的例子:`string1` 直到外部作用域结束都是有效的,`string2` 则在内部作用域中是有效的,而 `result` 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 `The longest string is long string is long`
让我们看看如何通过传递拥有不同具体生命周期的引用来限制 `longest` 函数的使用。示例 10-24 是一个应该在任何编程语言中都很直观的例子:`string1` 直到外部作用域结束都是有效的,`string2` 则在内部作用域中是有效的,而 `result` 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 `The longest string is long string is long`
<span class="filename">文件名: src/main.rs</span>
@ -428,7 +428,7 @@ fn longest(x: &str, y: &str) -> &str {
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
```
再来应用第二条规则,它并不适用因为存在多于一个输入生命周期。再来看第三条规则,它同样也不适用因为没有 `self` 参数。然后我们就没有更多规则了,不过还没有计算出返回值的类型的生命周期。这就是为什么在编译示例 10-22 的代码时会出现错误的原因:编译器用所有已知的生命周期省略规则,不过仍然不能计算出签名中所有引用的生命周期。
再来应用第二条规则,它并不适用因为存在多于一个输入生命周期。再来看第三条规则,它同样也不适用因为没有 `self` 参数。然后我们就没有更多规则了,不过还没有计算出返回值的类型的生命周期。这就是为什么在编译示例 10-22 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,不过仍然不能计算出签名中所有引用的生命周期。
因为第三条规则真正能够适用的就只有方法签名,现在就让我们看看那种情况中的生命周期,并看看为什么这条规则意味着我们经常不需要在方法签名中标注生命周期。
@ -442,7 +442,7 @@ parameters need to be declared and used since the lifetime parameters could go
with the struct's fields or with references passed into or returned from
methods. /Carol -->
当为带有生命周期的结构体实现方法时,其语法依然类似示例 10-11 中展示的泛型类型参数的语法:声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关
当为带有生命周期的结构体实现方法时,其语法依然类似示例 10-11 中展示的泛型类型参数的语法:声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关
(实现方法时)结构体字段的生命周期必须总是在 `impl` 关键字之后声明并在结构体名称之后被使用,因为这些生命周期是结构体类型的一部分。

@ -208,6 +208,6 @@ fn it_adds_two() {
## 总结
Rust 的测试功能提供了一个如何确保即使函数做出改变也能继续以指定方式运行的途径。单元测试独立的验证库的不同部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来工作时的用例,并像其他外部码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免一些 bug不过测试对于减少代码是否符合期望相关的逻辑 bug 仍然是很重要的。
Rust 的测试功能提供了一个如何确保即使函数做出改变也能继续以指定方式运行的途径。单元测试独立的验证库的不同部分并能够测试私有实现细节。集成测试则涉及多个部分结合起来工作时的用例,并像其他外部码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免一些 bug不过测试对于减少代码是否符合期望相关的逻辑 bug 仍然是很重要的。
接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目!
接下来让我们结合本章所学和其他之前章节的知识,在下一章一起编写一个项目!

@ -84,7 +84,7 @@ fn main() {
正如之前打印出 vector 时所所看到的,程序的名称占据了 vector 的第一个值 `args[0]`,所以我们从索引 `1` 开始。`minigrep` 获取的第一个参数是需要搜索的字符串,所以将其将第一个参数的引用存放在变量 `query` 中。第二个参数将是文件名,所以将第二个参数的引用放入变量 `filename` 中。
我们将临时打印出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数 `test``sample.txt` 再次运行这个程序:
我们将临时打印出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数 `test``sample.txt` 再次运行这个程序:
```text
$ cargo run test sample.txt

@ -202,7 +202,7 @@ thread 'main' panicked at 'not enough arguments', src/main.rs:29
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
这个输出就好多了,现在有了一个合理的错误信息。然而,我们还有一堆额外的信息不希望提供给用户。所以在这里使用示例 9-8 中的技术可能不是最好的;无论如何 `panic!` 调用更适合程序上的问题而不是使用上的问题,正如第九章所讲到的。相反我们可以使用那一章学习的另一个技术:返回一个可以表明成功或错误的 `Result`
这个输出就好多了,现在有了一个合理的错误信息。然而,我们还有一堆额外的信息不希望提供给用户。所以在这里使用示例 9-8 中的技术可能不是最好的;正如第九章所讲到的一样,`panic!` 的调用更趋向于程序上的问题而不是使用上的问题。相反我们可以使用那一章学习的另一个技术:返回一个可以表明成功或错误的 `Result`
#### 从 `new` 中返回 `Result` 而不是调用 `panic!`
@ -442,4 +442,4 @@ fn main() {
哇哦!这可有很多的工作,不过我们为将来成功打下了基础。现在处理错误将更容易,同时代码也更加模块化。从现在开始几乎所有的工作都将在 *src/lib.rs* 中进行。
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,他们在新代码中却很简单:编写测试!
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,他们在新代码中却很简单:编写测试!

@ -97,7 +97,7 @@ fn generate_workout(intensity: i32, random_number: i32) {
如果用户需要高强度锻炼,这里有一些额外的逻辑:如果 app 生成的随机数刚好是 3app 相反会建议用户稍做休息并补充水分。如果不是,则用户会从复杂算法中得到数分钟跑步的高强度锻炼计划。
数据科学部门的同学告知我们必须对调用算法的方式做出一些改变。为了简化做出这些改变的更新,我们将重构代码来只调用 `simulated_expensive_calculation` 一次。同时还希望去掉目前多余的连续两次函数调用,并不希望在计算过程中增加任何其他此函数的调用。也就是说,我们不希望在完全无需其结果的情况调用函数,不过最终仍然需要调用函数一次。
数据科学部门的同学告知我们将来会对调用算法的方式做出一些改变。为了在要做这些改动的时候简化更新步骤,我们将重构代码来让它只调用 `simulated_expensive_calculation` 一次。同时还希望去掉目前多余的连续两次函数调用,并不希望在计算过程中增加任何其他此函数的调用。也就是说,我们不希望在完全无需其结果的情况调用函数,不过最终仍然需要调用函数一次。
有多种方法可以重构此程序。我们首先尝试的是将重复的慢计算函数调用提取到一个变量中,如示例 13-4 所示:
@ -210,7 +210,7 @@ fn generate_workout(intensity: i32, random_number: i32) {
<span class="caption">示例 13-6调用定义的 `expensive_closure`</span>
现在我们达成了将计算统一到一个地方的目标,并只会在需要结果的时候执行改代码。然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这会调用慢计算两次并使用户多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过正因为使用了闭包还有另一个解决方案。稍后会回到这个方案上;首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
现在我们达成了将计算统一到一个地方的目标,并只会在需要结果的时候执行改代码。然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这会调用慢计算两次并使用户多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过正因为使用了闭包还有另一个解决方案。稍后会回到这个方案上;首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
### 闭包类型推断和注解
@ -553,4 +553,4 @@ error[E0382]: use of moved value: `x`
大部分需要指定一个 `Fn` trait bound 的时候,可以从 `Fn` 开始,编译器会根据闭包体中的情况告诉你是否需要 `FnMut``FnOnce`
为了展示闭包作为函数参数时捕获其环境的作用,让我们移动到下一个主题:迭代器。
为了展示闭包作为函数参数时捕获其环境的作用,让我们移动到下一个主题:迭代器。

Loading…
Cancel
Save