mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-14 21:11:31 +08:00
Merge pull request #280 from yue2388253/master
improve some translation and fix typo
This commit is contained in:
commit
f45ee79d3b
@ -55,7 +55,7 @@ fn main() {
|
|||||||
|
|
||||||
<span class="caption">示例 13-2:`main` 函数包含了用于 `generate_workout` 函数的模拟用户输入和模拟随机数输入</span>
|
<span class="caption">示例 13-2:`main` 函数包含了用于 `generate_workout` 函数的模拟用户输入和模拟随机数输入</span>
|
||||||
|
|
||||||
处于简单考虑这里硬编码了 `simulated_user_specified_value` 变量的值为 10 和 `simulated_random_number` 变量的值为 7;一个实际的程序会从 app 前端获取强度系数并使用 `rand` crate 来生成随机数,正如第二章的猜猜看游戏所做的那样。`main` 函数使用模拟的输入值调用 `generate_workout` 函数:
|
出于简单考虑这里硬编码了 `simulated_user_specified_value` 变量的值为 10 和 `simulated_random_number` 变量的值为 7;一个实际的程序会从 app 前端获取强度系数并使用 `rand` crate 来生成随机数,正如第二章的猜猜看游戏所做的那样。`main` 函数使用模拟的输入值调用 `generate_workout` 函数:
|
||||||
|
|
||||||
现在有了执行上下文,让我们编写算法。示例 13-3 中的 `generate_workout` 函数包含本例中我们最关心的 app 业务逻辑。本例中余下的代码修改都将在这个函数中进行:
|
现在有了执行上下文,让我们编写算法。示例 13-3 中的 `generate_workout` 函数包含本例中我们最关心的 app 业务逻辑。本例中余下的代码修改都将在这个函数中进行:
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
|
|||||||
|
|
||||||
<span class="caption">示例 13-3:程序的业务逻辑,它根据输入并调用 `simulated_expensive_calculation` 函数来打印出健身计划</span>
|
<span class="caption">示例 13-3:程序的业务逻辑,它根据输入并调用 `simulated_expensive_calculation` 函数来打印出健身计划</span>
|
||||||
|
|
||||||
示例 13-3 中的代码有多处慢计算函数的调用。第一个 `if` 块调用了 `simulated_expensive_calculation` 两次,外部 `else` 中的 `if` 完全没有调用它,第二个 `else` 中的代码调用了它一次。
|
示例 13-3 中的代码有多处调用了慢计算函数 `simulated_expensive_calculation` 。第一个 `if` 块调用了 `simulated_expensive_calculation` 两次, `else` 中的 `if` 没有调用它,而第二个 `else` 中的代码调用了它一次。
|
||||||
|
|
||||||
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
|
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ let expensive_closure = |num| {
|
|||||||
|
|
||||||
闭包定义是 `expensive_closure` 赋值的 `=` 之后的部分。闭包的定义以一对竖线(`|`)开始,在竖线中指定闭包的参数;之所以选择这个语法是因为它与 Smalltalk 和 Ruby 的闭包定义类似。这个闭包有一个参数 `num`;如果有多于一个参数,可以使用逗号分隔,比如 `|param1, param2|`。
|
闭包定义是 `expensive_closure` 赋值的 `=` 之后的部分。闭包的定义以一对竖线(`|`)开始,在竖线中指定闭包的参数;之所以选择这个语法是因为它与 Smalltalk 和 Ruby 的闭包定义类似。这个闭包有一个参数 `num`;如果有多于一个参数,可以使用逗号分隔,比如 `|param1, param2|`。
|
||||||
|
|
||||||
参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。闭包体的最后一行(`num`)返回的值将是调用闭包时返回的值,因为最后一行没有分号;正如函数体中的一样。
|
参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 `let` 语句的分号。因为闭包体的最后一行没有分号(正如函数体一样),所以其返回了值 `num` 。
|
||||||
|
|
||||||
注意这个 `let` 语句意味着 `expensive_closure` 包含一个匿名函数的 **定义**,不是调用匿名函数的 **返回值**。回忆一下使用闭包的原因是我们需要在一个位置定义代码,储存代码,并在之后的位置实际调用它;期望调用的代码现在储存在 `expensive_closure` 中。
|
注意这个 `let` 语句意味着 `expensive_closure` 包含一个匿名函数的 **定义**,不是调用匿名函数的 **返回值**。回忆一下使用闭包的原因是我们需要在一个位置定义代码,储存代码,并在之后的位置实际调用它;期望调用的代码现在储存在 `expensive_closure` 中。
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
|
|||||||
|
|
||||||
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行改代码。
|
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行改代码。
|
||||||
|
|
||||||
然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这会调用慢计算两次并使用户多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过正因为使用了闭包还有另一个解决方案。稍后会回到这个方案上。首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
|
然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。我们稍后会讨论这个方案,不过目前让我们首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
|
||||||
|
|
||||||
### 闭包类型推断和注解
|
### 闭包类型推断和注解
|
||||||
|
|
||||||
@ -296,9 +296,9 @@ error[E0308]: mismatched types
|
|||||||
|
|
||||||
幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可能见过这种模式被称 *memoization* 或 *lazy evaluation*。
|
幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可能见过这种模式被称 *memoization* 或 *lazy evaluation*。
|
||||||
|
|
||||||
为了让结构体存放闭包,我们需要能够指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
|
为了让结构体存放闭包,我们需要指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
|
||||||
|
|
||||||
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在“闭包会捕获其环境”部分捕获环境部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
|
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在“闭包会捕获其环境”部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
|
||||||
|
|
||||||
为了满足 `Fn` trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包有一个 `u32` 的参数并返回一个 `u32`,这样所指定的 trait bound 就是 `Fn(u32) -> u32`。
|
为了满足 `Fn` trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包有一个 `u32` 的参数并返回一个 `u32`,这样所指定的 trait bound 就是 `Fn(u32) -> u32`。
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ struct Cacher<T>
|
|||||||
|
|
||||||
结构体 `Cacher` 有一个泛型 `T` 的字段 `calculation`。`T` 的 trait bound 指定了 `T` 是一个使用 `Fn` 的闭包。任何我们希望储存到 `Cacher` 实例的 `calculation` 字段的闭包必须有一个 `u32` 参数(由 `Fn` 之后的括号的内容指定)并必须返回一个 `u32`(由 `->` 之后的内容)。
|
结构体 `Cacher` 有一个泛型 `T` 的字段 `calculation`。`T` 的 trait bound 指定了 `T` 是一个使用 `Fn` 的闭包。任何我们希望储存到 `Cacher` 实例的 `calculation` 字段的闭包必须有一个 `u32` 参数(由 `Fn` 之后的括号的内容指定)并必须返回一个 `u32`(由 `->` 之后的内容)。
|
||||||
|
|
||||||
> 注意:函数也都实现了这三个 `Fn` trait。如果不需要捕获环境中的值,则在需要实现 `Fn` trait 是可以使用函数而不是闭包。
|
> 注意:函数也都实现了这三个 `Fn` trait。如果不需要捕获环境中的值,则可以使用实现了 `Fn` trait 的函数而不是闭包。
|
||||||
|
|
||||||
`value` 是 `Option<i32>` 类型的。在执行闭包之前,`value` 将是 `None`。如果使用 `Cacher` 的代码请求闭包的结果,这时会执行闭包并将结果储存在 `value` 字段的 `Some` 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 `Some` 成员中的结果。
|
`value` 是 `Option<i32>` 类型的。在执行闭包之前,`value` 将是 `None`。如果使用 `Cacher` 的代码请求闭包的结果,这时会执行闭包并将结果储存在 `value` 字段的 `Some` 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 `Some` 成员中的结果。
|
||||||
|
|
||||||
@ -442,7 +442,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
|
|||||||
|
|
||||||
### `Cacher` 实现的限制
|
### `Cacher` 实现的限制
|
||||||
|
|
||||||
值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 `Cacher` 的实现存在一些小问题,这使得在不同上下文中复用变得很困难。
|
值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 `Cacher` 的实现存在两个小问题,这使得在不同上下文中复用变得很困难。
|
||||||
|
|
||||||
第一个问题是 `Cacher` 实例假设对于 `value` 方法的任何 `arg` 参数值总是会返回相同的值。也就是说,这个 `Cacher` 的测试会失败:
|
第一个问题是 `Cacher` 实例假设对于 `value` 方法的任何 `arg` 参数值总是会返回相同的值。也就是说,这个 `Cacher` 的测试会失败:
|
||||||
|
|
||||||
@ -470,7 +470,7 @@ thread 'call_with_different_values' panicked at 'assertion failed: `(left == rig
|
|||||||
|
|
||||||
这里的问题是第一次使用 1 调用 `c.value`,`Cacher` 实例将 `Some(1)` 保存进 `self.value`。在这之后,无论传递什么值调用 `value`,它总是会返回 1。
|
这里的问题是第一次使用 1 调用 `c.value`,`Cacher` 实例将 `Some(1)` 保存进 `self.value`。在这之后,无论传递什么值调用 `value`,它总是会返回 1。
|
||||||
|
|
||||||
尝试修改 `Cacher` 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 `arg` 值,而 value 则是对应 key 调用闭包的结果值。相比之前检查 `self.value` 直接是 `Some` 还是 `None` 值,现在 `value` 会在哈希 map 中寻找 `arg`,如果存在就返回它。如果不存在,`Cacher` 会调用闭包并将结果值保存在哈希 map 对应 `arg` 值的位置。
|
尝试修改 `Cacher` 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 `arg` 值,而 value 则是对应 key 调用闭包的结果值。相比之前检查 `self.value` 直接是 `Some` 还是 `None` 值,现在 `value` 函数会在哈希 map 中寻找 `arg`,如果找到的话就返回其对应的值。如果不存在,`Cacher` 会调用闭包并将结果值保存在哈希 map 对应 `arg` 值的位置。
|
||||||
|
|
||||||
当前 `Cacher` 实现的第二个问题是它的应用被限制为只接受获取一个 `u32` 值并返回一个 `u32` 值的闭包。比如说,我们可能需要能够缓存一个获取字符串 slice 并返回 `usize` 值的闭包的结果。请尝试引入更多泛型参数来增加 `Cacher` 功能的灵活性。
|
当前 `Cacher` 实现的第二个问题是它的应用被限制为只接受获取一个 `u32` 值并返回一个 `u32` 值的闭包。比如说,我们可能需要能够缓存一个获取字符串 slice 并返回 `usize` 值的闭包的结果。请尝试引入更多泛型参数来增加 `Cacher` 功能的灵活性。
|
||||||
|
|
||||||
@ -527,7 +527,7 @@ error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
|
|||||||
|
|
||||||
编译器甚至会提示我们这只能用于闭包!
|
编译器甚至会提示我们这只能用于闭包!
|
||||||
|
|
||||||
当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,当执行不会捕获环境的更通用的代码场景中我们不希望有这些开销。因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
|
当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,在更一般的场景中,当我们不需要闭包来捕获环境时,我们不希望产生这些开销。因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
|
||||||
|
|
||||||
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用。这三种捕获值的方式被编码为如下三个 `Fn` trait:
|
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用。这三种捕获值的方式被编码为如下三个 `Fn` trait:
|
||||||
|
|
||||||
@ -557,7 +557,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这个例子并不能编译:
|
这个例子并不能编译,会产生以下错误:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
error[E0382]: use of moved value: `x`
|
error[E0382]: use of moved value: `x`
|
||||||
@ -575,6 +575,6 @@ error[E0382]: use of moved value: `x`
|
|||||||
|
|
||||||
`x` 被移动进了闭包,因为闭包使用 `move` 关键字定义。接着闭包获取了 `x` 的所有权,同时 `main` 就不再允许在 `println!` 语句中使用 `x` 了。去掉 `println!` 即可修复问题。
|
`x` 被移动进了闭包,因为闭包使用 `move` 关键字定义。接着闭包获取了 `x` 的所有权,同时 `main` 就不再允许在 `println!` 语句中使用 `x` 了。去掉 `println!` 即可修复问题。
|
||||||
|
|
||||||
大部分需要指定一个 `Fn` trait bound 的时候,可以从 `Fn` 开始,而编译器会根据闭包体中的情况告诉你是否需要 `FnMut` 或 `FnOnce`。
|
大部分需要指定一个 `Fn` 系列 trait bound 的时候,可以从 `Fn` 开始,而编译器会根据闭包体中的情况告诉你是否需要 `FnMut` 或 `FnOnce`。
|
||||||
|
|
||||||
为了展示闭包作为函数参数时捕获其环境的作用,让我们移动到下一个主题:迭代器。
|
为了展示闭包作为函数参数时捕获其环境的作用,让我们继续下一个主题:迭代器。
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
|
迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
|
||||||
|
|
||||||
在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
|
在 Rust 中,迭代器是**惰性的**(*lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let v1 = vec![1, 2, 3];
|
let v1 = vec![1, 2, 3];
|
||||||
@ -16,7 +16,7 @@ let v1_iter = v1.iter();
|
|||||||
|
|
||||||
<span class="caption">示例 13-13:创建一个迭代器</span>
|
<span class="caption">示例 13-13:创建一个迭代器</span>
|
||||||
|
|
||||||
一旦创建迭代器之后,可以选择用多种方式利用它。在示例 3-4 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,虽然直到现在为止我们一直掩盖了 `iter` 调用做了什么。
|
一旦创建迭代器之后,可以选择用多种方式利用它。在示例 3-4 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,虽然直到现在为止我们一直没有具体讨论调用 `iter` 到底具体做了什么。
|
||||||
|
|
||||||
示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
|
示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ fn filters_by_size() {
|
|||||||
|
|
||||||
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。
|
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。
|
||||||
|
|
||||||
在 `shoes_in_my_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成只含有闭包返回 `true` 元素的新迭代器。
|
`shoes_in_my_size` 函数调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成一个只含有那些闭包返回 `true` 的元素的新迭代器。
|
||||||
|
|
||||||
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
|
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
|
||||||
|
|
||||||
|
@ -31,11 +31,11 @@ impl Config {
|
|||||||
|
|
||||||
<span class="caption">示例 13-24:重现第十二章结尾的 `Config::new` 函数</span>
|
<span class="caption">示例 13-24:重现第十二章结尾的 `Config::new` 函数</span>
|
||||||
|
|
||||||
这时可以不必担心低效的 `clone` 调用了,因为将来可以去掉他们。好吧,就是现在!
|
那时我们说过不必担心低效的 `clone` 调用了,因为将来可以对他们进行改进。好吧,就是现在!
|
||||||
|
|
||||||
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice,而 `new` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query` 和 `filename` 的值,这样 `Config` 实例就能拥有这些值。
|
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice,而 `new` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query` 和 `filename` 的值,这样 `Config` 实例就能拥有这些值。
|
||||||
|
|
||||||
通过迭代器的新知识,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::new` 的工作因为迭代器会负责访问这些值。
|
在学习了迭代器之后,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::new` 的工作因为迭代器会负责访问这些值。
|
||||||
|
|
||||||
一旦 `Config::new` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。
|
一旦 `Config::new` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们会修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。直到同步更新 `Config::new` 之前这些代码还不能编译:
|
修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。在更新 `Config::new` 之前这些代码还不能编译:
|
||||||
|
|
||||||
<span class="filename">文件名: src/main.rs</span>
|
<span class="filename">文件名: src/main.rs</span>
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ impl Config {
|
|||||||
|
|
||||||
### 使用迭代器适配器来使代码更简明
|
### 使用迭代器适配器来使代码更简明
|
||||||
|
|
||||||
I/O 项目中其他可以利用迭代器优势的地方位于 `search` 函数,示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
|
I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
|
||||||
|
|
||||||
<span class="filename">文件名: src/lib.rs</span>
|
<span class="filename">文件名: src/lib.rs</span>
|
||||||
|
|
||||||
@ -168,8 +168,8 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
|||||||
|
|
||||||
<span class="caption">示例 13-29:在 `search` 函数实现中使用迭代器适配器</span>
|
<span class="caption">示例 13-29:在 `search` 函数实现中使用迭代器适配器</span>
|
||||||
|
|
||||||
回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-19 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 返回 `true` 的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!请随意对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改。
|
回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-19 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 返回 `true` 的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!尝试对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改吧。
|
||||||
|
|
||||||
接下来的逻辑问题就是在代码中应该选择哪种风格:示例 13-28 中的原始实现,或者是示例 13-29 中使用迭代器的版本。大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的目的。这抽象出了那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须面对的过滤条件。
|
接下来的逻辑问题就是在代码中应该选择哪种风格:是使用示例 13-28 中的原始实现还是使用示例 13-29 中使用迭代器的版本?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的目的。这抽象出了那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须面对的过滤条件。
|
||||||
|
|
||||||
不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。
|
不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||||
|
|
||||||
为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快:直接使用 `for` 循环的版本还是使用迭代器的版本。
|
为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快一些:是直接使用 `for` 循环的版本还是使用迭代器的版本。
|
||||||
|
|
||||||
我们运行了一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进 `String` 并寻找其中的单词 “the”。如下是 `for` 循环版本和迭代器版本的 `search` 函数的性能测试结果:
|
我们运行了一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进 `String` 并寻找其中的单词 “the”。如下是 `for` 循环版本和迭代器版本的 `search` 函数的性能测试结果:
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
|
|||||||
|
|
||||||
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
|
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
|
||||||
|
|
||||||
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会强加运行时开销,它与本贾尼·斯特劳斯特卢普,C++ 的设计和实现者所定义的 **零开销**(*zero-overhead*)如出一辙:
|
对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会引入运行时开销,它与本贾尼·斯特劳斯特卢普(C++ 的设计和实现者)在 “Foundations of C++”(2012) 中所定义的 **零开销**(*zero-overhead*)如出一辙:
|
||||||
|
|
||||||
> In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
|
> In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
|
||||||
>
|
>
|
||||||
|
@ -12,4 +12,4 @@
|
|||||||
* 从 [crates.io](https://crates.io)<!-- ignore --> 安装二进制文件
|
* 从 [crates.io](https://crates.io)<!-- ignore --> 安装二进制文件
|
||||||
* 使用自定义的命令来扩展 Cargo
|
* 使用自定义的命令来扩展 Cargo
|
||||||
|
|
||||||
相比本章能够涉及的工作 Cargo 甚至还可以做到更多,关于其功能的全部解释,请查看 [文档](http://doc.rust-lang.org/cargo/)
|
Cargo 的功能不止本章所介绍的,关于其功能的全部解释,请查看 [文档](http://doc.rust-lang.org/cargo/)
|
@ -98,20 +98,20 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
|||||||
|
|
||||||
### 使用 `pub use` 导出合适的公有 API
|
### 使用 `pub use` 导出合适的公有 API
|
||||||
|
|
||||||
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而对你开发来说很有道理的结果可能对用户来说就不太方便了。你可能希望将结构组织进有多个层次的层级中,不过想要使用被定义在很深层级中的类型的人可能很难发现这些类型是否存在。他们也可能会厌烦使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
|
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用于来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
|
||||||
|
|
||||||
公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
|
公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
|
||||||
|
|
||||||
好消息是,如果结果对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出(re-export)项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。
|
好消息是,即使文件结构对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出(re-export)项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。
|
||||||
|
|
||||||
例如,假设我们创建了一个模块化的充满艺术化气息的库 `art`。在这个库中是一个包含两个枚举 `PrimaryColor` 和 `SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
|
例如,假设我们创建了一个描述美术信息的库 `art`。这个库中包含了一个有两个枚举 `PrimaryColor` 和 `SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
|
||||||
|
|
||||||
<span class="filename">文件名: src/lib.rs</span>
|
<span class="filename">文件名: src/lib.rs</span>
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
//! # Art
|
//! # Art
|
||||||
//!
|
//!
|
||||||
//! 一个模块化的充满艺术化气息的库。
|
//! 一个描述美术信息的库。
|
||||||
|
|
||||||
pub mod kinds {
|
pub mod kinds {
|
||||||
/// 采用 RGB 色彩模式的主要颜色。
|
/// 采用 RGB 色彩模式的主要颜色。
|
||||||
@ -148,7 +148,7 @@ pub mod utils {
|
|||||||
|
|
||||||
<span class="caption">图 14-3:包含 `kinds` 和 `utils` 模块的库 `art` 的文档首页</span>
|
<span class="caption">图 14-3:包含 `kinds` 和 `utils` 模块的库 `art` 的文档首页</span>
|
||||||
|
|
||||||
注意 `PrimaryColor` 和 `SecondaryColor` 类型没有在首页中列出,`mix` 函数也没有。必须点击 `kinds` 或 `utils` 才能看到他们。
|
注意 `PrimaryColor` 和 `SecondaryColor` 类型、以及 `mix` 函数都没有在首页中列出。我们必须点击 `kinds` 或 `utils` 才能看到他们。
|
||||||
|
|
||||||
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-4 展示了一个使用 `art` crate 中 `PrimaryColor` 和 `mix` 项的 crate 的例子:
|
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-4 展示了一个使用 `art` crate 中 `PrimaryColor` 和 `mix` 项的 crate 的例子:
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ fn main() {
|
|||||||
```rust,ignore
|
```rust,ignore
|
||||||
//! # Art
|
//! # Art
|
||||||
//!
|
//!
|
||||||
//! 一个模块化的充满艺术化气息的库。
|
//! 一个描述美术信息的库。
|
||||||
|
|
||||||
pub use kinds::PrimaryColor;
|
pub use kinds::PrimaryColor;
|
||||||
pub use kinds::SecondaryColor;
|
pub use kinds::SecondaryColor;
|
||||||
@ -216,7 +216,7 @@ fn main() {
|
|||||||
|
|
||||||
对于有很多嵌套模块的情况,使用 `pub use` 将类型重导出到顶级结构对于使用 crate 的人来说将会是大为不同的体验。
|
对于有很多嵌套模块的情况,使用 `pub use` 将类型重导出到顶级结构对于使用 crate 的人来说将会是大为不同的体验。
|
||||||
|
|
||||||
创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。选择 `pub use` 提供了解耦组织 crate 内部结构和与终端用户体现的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
|
创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。`pub use` 提供了解耦组织 crate 内部结构和与终端用户体现的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
|
||||||
|
|
||||||
### 创建 Crates.io 账号
|
### 创建 Crates.io 账号
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
|
|
||||||
现在我们创建了一个账号,保存了 API token,为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io)<!-- ignore --> 以供他人使用。
|
现在我们创建了一个账号,保存了 API token,为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io)<!-- ignore --> 以供他人使用。
|
||||||
|
|
||||||
发布 crate 时请多加小心,因为发布是 **永久性的**(*permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中 crate 的项目都能一直正常工作。允许删除版本将不可能满足这个目标。然而,可以被发布的版本号却没有限制。
|
发布 crate 时请多加小心,因为发布是 **永久性的**(*permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
|
||||||
|
|
||||||
再次运行 `cargo publish` 命令。这次它应该会成功:
|
再次运行 `cargo publish` 命令。这次它应该会成功:
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ add-one = { path = "../add-one" }
|
|||||||
|
|
||||||
工作空间中的 crate 不必相互依赖,所以仍需显式地表明工作空间中 crate 的依赖关系。
|
工作空间中的 crate 不必相互依赖,所以仍需显式地表明工作空间中 crate 的依赖关系。
|
||||||
|
|
||||||
接下来,在 `adder` crate 中使用 `add-one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `extern crate` 将新 `add-one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示:
|
接下来,在 `adder` crate 中使用 `add-one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `use` 将新 `add-one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示:
|
||||||
|
|
||||||
<span class="filename">文件名: adder/src/main.rs</span>
|
<span class="filename">文件名: adder/src/main.rs</span>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||||
|
|
||||||
Cargo 被设计为可以通过新的子命令而无须修改 Cargo 自身来进行扩展。如果 `$PATH` 中有类似 `cargo-something` 的二进制文件,就可以通过 `cargo something` 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 `cargo --list` 来展示出来。能够通过 `cargo install` 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!
|
Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩展,而无需修改 Cargo 本身。如果 `$PATH` 中有类似 `cargo-something` 的二进制文件,就可以通过 `cargo something` 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 `cargo --list` 来展示出来。能够通过 `cargo install` 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!
|
||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ impl Messenger for MockMessenger {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<span class="caption">示例 15-26:在同一作用域中创建两个可变引用并观察 `RefCell<T>` panic</span>
|
<span class="caption">示例 15-23:在同一作用域中创建两个可变引用并观察 `RefCell<T>` panic</span>
|
||||||
|
|
||||||
这里为 `borrow_mut` 返回的 `RefMut` 智能指针创建了 `one_borrow` 变量。接着用相同的方式在变量 `two_borrow` 创建了另一个可变借用。这会在相同作用域中创建一个可变引用,这是不允许的。当运行库的测试时,示例 15-23 编译时不会有任何错误,不过测试会失败:
|
这里为 `borrow_mut` 返回的 `RefMut` 智能指针创建了 `one_borrow` 变量。接着用相同的方式在变量 `two_borrow` 创建了另一个可变借用。这会在相同作用域中创建一个可变引用,这是不允许的。当运行库的测试时,示例 15-23 编译时不会有任何错误,不过测试会失败:
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||||
|
|
||||||
Rust 的内存安全保证使其难以意外的制造永远也不会被清理的内存(被称为 **内存泄露**(*memory leak*)),但并不是不可能。完全的避免内存泄露并不是同在编译时拒绝数据竞争一样为 Rust 的保证之一,这意味着内存泄露在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:有可能会创建个个项之间相互引用的引用。这会造成内存泄露,因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。
|
Rust 的内存安全保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄露**(*memory leak*)),但并不是不可能。完全地避免内存泄露并不是同在编译时拒绝数据竞争一样为 Rust 的保证之一,这意味着内存泄露在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:有可能会创建个个项之间相互引用的引用。这会造成内存泄露,因为每一项的引用计数将永远也到不了 0,其值也永远也不会被丢弃。
|
||||||
|
|
||||||
### 制造引用循环
|
### 制造引用循环
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user