diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index 7424ef1..3ec2d56 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -4,11 +4,11 @@ >
> commit 2cd1b5593d26dc6a03c20f8619187ad4b2485552 -有了这些关于迭代器的新知识,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::build` 函数和 `search` 函数的实现。 +掌握了这些关于迭代器的新知识后,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。接下来,让我们看看迭代器如何改进 `Config::build` 函数和 `search` 函数的实现。 -### 使用迭代器并去掉 `clone` +### 使用迭代器去除 `clone` -在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,它们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-17 中重现了第十二章结尾示例 12-23 中 `Config::build` 函数的实现: +在示例 12-6 中,我们增加了一些代码获取一个 `String` 类型的 slice 并创建一个 `Config` 结构体的实例,它们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-17 中重现了第十二章结尾示例 12-23 中 `Config::build` 函数的实现: 文件名:src/lib.rs @@ -18,11 +18,11 @@ 示例 13-17:重现示例 12-23 的 `Config::build` 函数 -那时我们说过不必担心低效的 `clone` 调用了,因为将来可以对它们进行改进。好吧,就是现在! +当时我们说过不必担心低效的 `clone` 调用,因为我们以后会将其移除。好吧,就是现在! 起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice,而 `build` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query` 和 `file_path` 的值,这样 `Config` 实例就能拥有这些值。 -在学习了迭代器之后,我们可以将 `build` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::build` 的工作因为迭代器会负责访问这些值。 +在学习了迭代器之后,我们可以将 `build` 函数改为获取一个有所有权的迭代器作为参数,而不是借用 slice。我们将使用迭代器功能代替之前检查 slice 长度和索引特定位置的代码。这样可以更清晰地表达 `Config::build` 函数的操作,因为迭代器会负责访问这些值。 一旦 `Config::build` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。 @@ -48,7 +48,7 @@ `env::args` 函数返回一个迭代器!不同于将迭代器的值收集到一个 vector 中接着传递一个 slice 给 `Config::build`,现在我们直接将 `env::args` 返回的迭代器的所有权传递给 `Config::build`。 -接下来需要更新 `Config::build` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::build` 的签名改为如示例 13-19 所示。这仍然不能编译因为我们还需更新函数体: +接下来需要更新 `Config::build` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::build` 的签名改为如示例 13-19 所示。这仍然不能编译因为我们还需更新函数体。 文件名:src/lib.rs @@ -58,15 +58,15 @@ 示例 13-19:以迭代器作为参数更新 `Config::build` 的签名 -`env::args` 函数的标准库文档显示,它返回的迭代器的类型为 `std::env::Args`,同时这个类型实现了 `Iterator` trait 并返回 `String` 值。 +`env::args` 函数的标准库文档显示,它返回的迭代器的类型为 `std::env::Args`,并且这个类型实现了 `Iterator` trait 并返回 `String` 值。 -我们已经更新了 `Config::build` 函数的签名,因此参数 `args` 有一个带有 trait bound `impl Iterator` 的泛型类型,而不是 `&[String]`。这里用到了第十章 [“trait 作为参数”][impl-trait] 部分讨论过的 `impl Trait` 语法意味着 `args` 可以是任何实现了 `Iterator` 的类型并返回 `String` 项(item)。 +我们已经更新了 `Config::build` 函数的签名,因此参数 `args` 有一个带有 trait bounds `impl Iterator` 的泛型类型,而不是 `&[String]`。这里用到了第十章 [“trait 作为参数”][impl-trait] 部分讨论过的 `impl Trait` 语法,这意味着 `args` 可以是任何实现了 `Iterator` trait 并返回 `String` 项(item)的类型。 - 因为我们拥有 `args` 的所有权,并且将通过对其进行迭代来改变 `args` ,所以我们可以将 `mut` 关键字添加到 `args` 参数的规范中以使其可变。 +由于我们获取了 `args` 的所有权,并且将通过迭代来修改 `args`,因此我们可以在 `args` 参数的声明中添加 `mut` 关键字,使其可变。 #### 使用 `Iterator` trait 代替索引 -接下来,我们将修改 `Config::build` 的内容。因为 `args` 实现了 `Iterator` trait,因此我们知道可以对其调用 `next` 方法!示例 13-20 更新了示例 12-23 中的代码,以使用 `next` 方法: +接下来,我们将修改 `Config::build` 的函数体。因为 `args` 实现了 `Iterator` trait,因此我们知道可以对其调用 `next` 方法!示例 13-20 更新了示例 12-23 中的代码,以使用 `next` 方法: 文件名:src/lib.rs @@ -76,7 +76,7 @@ 示例 13-20:修改 `Config::build` 的函数体来使用迭代器方法 -请记住 `env::args` 返回值的第一个值是程序的名称。我们希望忽略它并获取下一个值,所以首先调用 `next` 并不对返回值做任何操作。之后对希望放入 `Config` 中字段 `query` 调用 `next`。如果 `next` 返回 `Some`,使用 `match` 来提取其值。如果它返回 `None`,则意味着没有提供足够的参数并通过 `Err` 值提早返回。对 `file_path` 值进行同样的操作。 +请记住 `env::args` 返回值的第一个值是程序的名称。我们希望忽略它并获取下一个值,所以首先调用 `next` 且不对其返回值做任何操作。然后,我们再次调用 `next` 来获取要放入 `Config` 结构体的 `query` 字段的值。如果 `next` 返回 `Some`,使用 `match` 来提取其值。如果它返回 `None`,则意味着没有提供足够的参数并通过 `Err` 值提早返回。我们对对 `file_path` 的值也进行同样的操作。 ### 使用迭代器适配器来使代码更简明 @@ -90,7 +90,7 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13 示例 13-21:示例 12-19 中 `search` 函数的定义 -可以通过使用迭代器适配器方法来编写更简明的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 `results` vector 的并发访问。示例 13-22 展示了该变化: +可以通过使用迭代器适配器方法来编写更简明的代码。这样做还可以避免使用一个可变的中间 `results` vector。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去除可变状态可能会使未来的并行搜索优化变得更容易,因为我们不必管理对 `results` vector 的并发访问。示例 13-22 展示了这一变化: 文件名:src/lib.rs @@ -100,12 +100,12 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13 示例 13-22:在 `search` 函数实现中使用迭代器适配器 -回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-16 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 返回 `true` 的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!尝试对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改吧。 +回忆一下,`search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于示例 13-16 中的 `filter` 例子,这段代码使用 `filter` 适配器来保留 `line.contains(query)` 返回 `true` 的行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!尝试对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改吧。 ### 选择循环或迭代器 -接下来的逻辑问题就是在代码中应该选择哪种风格:是使用示例 13-21 中的原始实现还是使用示例 13-22 中使用迭代器的版本?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的目的。这抽象掉那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须面对的过滤条件。 +接下来的逻辑问题就是在代码中应该选择哪种风格,以及原因:是使用示例 13-21 中的原始实现还是使用示例 13-22 中使用迭代器的版本?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以掌握,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器反而更容易理解。相比摆弄不同的循环并创建新 vector,(迭代器)代码则更关注循环的高层次目的。这抽象掉那些老生常谈的代码,这样就更容易看清代码所特有的概念,比如迭代器中每个元素必须满足的过滤条件。 -不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。 +不过这两种实现真的完全等价吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。 [impl-trait]: ch10-02-traits.html#trait-作为参数