From 79dac70d397b430991334e82d8c61c573ce1429b Mon Sep 17 00:00:00 2001 From: KaiserY Date: Fri, 23 May 2025 23:27:41 +0800 Subject: [PATCH] wip: 2024 edition --- src/ch11-01-writing-tests.md | 4 +- src/ch11-02-running-tests.md | 12 +++--- src/ch11-03-test-organization.md | 43 +++++++++---------- src/ch12-00-an-io-project.md | 23 +++++----- ...h12-01-accepting-command-line-arguments.md | 21 +++++---- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/ch11-01-writing-tests.md b/src/ch11-01-writing-tests.md index f3d2b9d..5e18167 100644 --- a/src/ch11-01-writing-tests.md +++ b/src/ch11-01-writing-tests.md @@ -57,7 +57,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下 `0 measured` 统计是针对性能测试的。性能测试(benchmark tests)在编写本书时,仍只能用于 Rust 开发版(nightly Rust)。请查看 [性能测试的文档][bench] 了解更多。 -我们可以将参数传递给 `cargo test` 命令,以便只运行名称与字符串匹配的测试;这就是所谓的**过滤**(_filtering_),我们会在 [“通过指定名字来运行部分测试”][subset] 讨论这一点。这里我们没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。 +我们可以将参数传递给 `cargo test` 命令,以便只运行名称与字符串匹配的测试;这就是所谓的**过滤**(_filtering_),我们会在 [“通过名称运行部分测试”][subset] 讨论这一点。这里我们没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。 测试输出中的以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。 @@ -323,7 +323,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下 ch08-02-strings.html#使用--运算符或-format-宏拼接字符串 [bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html [ignoring]: ch11-02-running-tests.html#除非特别指定否则忽略某些测试 -[subset]: ch11-02-running-tests.html#通过指定名字来运行部分测试 +[subset]: ch11-02-running-tests.html#通过名称运行部分测试 [controlling-how-tests-are-run]: ch11-02-running-tests.html#控制测试如何运行 [derivable-traits]: appendix-03-derivable-traits.html diff --git a/src/ch11-02-running-tests.md b/src/ch11-02-running-tests.md index d76caa1..e9de53d 100644 --- a/src/ch11-02-running-tests.md +++ b/src/ch11-02-running-tests.md @@ -58,7 +58,7 @@ $ cargo test -- --show-output {{#include ../listings/ch11-writing-automated-tests/output-only-01-show-output/output.txt}} ``` -### 通过指定名字来运行部分测试 +### 通过名称运行部分测试 有时运行整个测试集会耗费很长时间。如果你负责特定位置的代码,你可能会希望只运行与这些代码相关的测试。你可以向 `cargo test` 传递所希望运行的测试名称的参数来选择运行哪些测试。 @@ -86,9 +86,9 @@ $ cargo test -- --show-output {{#include ../listings/ch11-writing-automated-tests/output-only-02-single-test/output.txt}} ``` -只有名称为 `one_hundred` 的测试被运行了;因为其余两个测试并不匹配这个名称。测试输出在摘要行的结尾显示了 `2 filtered out` 表明还存在比本次所运行的测试更多的测试没有被运行。 +只有名称为 `one_hundred` 的测试被运行了;因为其余两个测试并不匹配这个名称。测试输出在末尾显示了 `2 filtered out`,表明还有 2 个测试被过滤,未被运行。 -不能像这样指定多个测试名称;只有传递给 `cargo test` 的第一个值才会被使用。不过有运行多个测试的方法。 +不能像这样指定多个测试名称;只有传递给 `cargo test` 的第一个值才会被使用。不过有个运行多个测试的方法。 #### 过滤运行多个测试 @@ -98,7 +98,7 @@ $ cargo test -- --show-output {{#include ../listings/ch11-writing-automated-tests/output-only-03-multiple-tests/output.txt}} ``` -这运行了所有名字中带有 `add` 的测试,也过滤掉了名为 `one_hundred` 的测试。同时注意测试所在的模块也是测试名称的一部分,所以可以通过模块名来运行一个模块中的所有测试。 +这运行了所有名字中带有 `add` 的测试,也过滤掉了名为 `one_hundred` 的测试。同时注意测试所在的模块也是测试名称的一部分,所以可以通过过滤模块名来运行一个模块中的所有测试。 ### 除非特别指定否则忽略某些测试 @@ -107,7 +107,7 @@ $ cargo test -- --show-output 文件名:src/lib.rs ```rust,noplayground -{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-11-ignore-a-test/src/lib.rs}} +{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-11-ignore-a-test/src/lib.rs:here}} ``` 对于想要排除的测试,我们在 `#[test]` 之后增加了 `#[ignore]` 行。现在如果运行测试,就会发现 `it_works` 运行了,而 `expensive_test` 没有运行: @@ -122,4 +122,4 @@ $ cargo test -- --show-output {{#include ../listings/ch11-writing-automated-tests/output-only-04-running-ignored/output.txt}} ``` -通过控制运行哪些测试,你可以确保能够快速地运行 `cargo test` 。当你需要运行 `ignored` 的测试时,可以执行 `cargo test -- --ignored`。如果你希望不管是否忽略都要运行全部测试,可以运行 `cargo test -- --include-ignored`。 +通过控制运行哪些测试,你可以确保 `cargo test` 的结果能够快速返回。当你需要运行 `ignored` 的测试时,可以执行 `cargo test -- --ignored`。如果你希望不管是否忽略都要运行全部测试,可以运行 `cargo test -- --include-ignored`。 diff --git a/src/ch11-03-test-organization.md b/src/ch11-03-test-organization.md index 32f4517..00ab08d 100644 --- a/src/ch11-03-test-organization.md +++ b/src/ch11-03-test-organization.md @@ -1,10 +1,9 @@ ## 测试的组织结构 -> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/main/src/ch11-03-test-organization.md) ->
-> commit 654d8902d380dbb8dd94ed2e548dfc0aa80c07cb + + -本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试**(*unit tests*)与 **集成测试**(*integration tests*)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。 +本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的术语和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试**(*unit tests*)与**集成测试**(*integration tests*)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,并且可以测试私有接口。而**集成测试**对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。 为了保证你的库能够按照你的预期运行,从独立和整体的角度编写这两类测试都是非常重要的。 @@ -24,11 +23,11 @@ {{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}} ``` -上述代码就是自动生成的测试模块。`cfg` 属性代表*配置*(*configuration*) ,它告诉 Rust,接下来的项,只有在给定特定配置选项时,才会被包含。在这种情况下,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性,Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数,以及标注为 `#[test]` 的函数。 +上述代码就是自动生成的测试模块。`cfg` 属性代表**配置**(*configuration*),它告诉 Rust 接下来的项只有在给定特定配置选项时,才会被包含。在这种情况下,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性,Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的辅助函数,以及标注为 `#[test]` 的函数。 #### 测试私有函数 -测试社区中一直存在关于是否应该对私有函数直接进行测试的论战,而在其他语言中想要测试私有函数是一件困难的,甚至是不可能的事。不过无论你坚持哪种测试意识形态,Rust 的私有性规则确实允许你测试私有函数。考虑示例 11-12 中带有私有函数 `internal_adder` 的代码: +测试社区中一直存在关于是否应该对私有函数直接进行测试的论战,而在其他语言中想要测试私有函数是一件困难的,甚至是不可能的事。不过无论你坚持哪种测试意识形态,Rust 的私有性规则确实允许你测试私有函数。考虑示例 11-12 中带有私有函数 `internal_adder` 的代码。 文件名:src/lib.rs @@ -38,7 +37,7 @@ 示例 11-12:测试私有函数 -注意 `internal_adder` 函数并没有标记为 `pub`。测试也不过是 Rust 代码,同时 `tests` 也仅仅是另一个模块。正如 [“路径用于引用模块树中的项”][paths] 部分所说,子模块的项可以使用其上级模块的项。在测试中,我们通过 `use super::*` 将 `tests` 模块的父模块的所有项引入了作用域,接着测试调用了 `internal_adder`。如果你并不认为应该测试私有函数,Rust 也不会强迫你这么做。 +注意 `internal_adder` 函数并没有标记为 `pub`。测试也不过是 Rust 代码,同时 `tests` 也仅仅是另一个模块。正如[“路径用于引用模块树中的项”][paths] 部分所说,子模块的项可以使用其上级模块的项。在测试中,我们通过 `use super::*` 将 `tests` 模块的父模块的所有项引入了作用域,接着测试调用了 `internal_adder`。如果你并不认为应该测试私有函数,Rust 也不会强迫你这么做。 ### 集成测试 @@ -55,7 +54,7 @@ adder ├── Cargo.lock ├── Cargo.toml ├── src -│   └── lib.rs +│ └── lib.rs └── tests └── integration_test.rs ``` @@ -70,7 +69,7 @@ adder 示例 11-13:一个 `adder` crate 中函数的集成测试 -因为每一个 `tests` 目录中的测试文件都是完全独立的 crate,所以需要在每一个文件中导入库。为此与单元测试不同,我们需要在文件顶部添加 `use adder`。 +因为每一个 *tests* 目录中的测试文件都是完全独立的 crate,所以需要将库引入到每个测试 crate 的作用域中。为此与单元测试不同,我们需要在文件顶部添加 `use adder::add_two;`,这在单元测试中是不需要的。 并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]`。 `tests` 文件夹在 Cargo 中是一个特殊的文件夹,Cargo 只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试: @@ -92,13 +91,13 @@ adder {{#include ../listings/ch11-writing-automated-tests/output-only-05-single-integration/output.txt}} ``` -这个命令只运行了 *tests* 目录中我们指定的文件 `integration_test.rs` 中的测试。 +这个命令只运行 *tests/integration_test.rs* 文件中的测试。 #### 集成测试中的子模块 -随着集成测试的增加,你可能希望在 `tests` 目录创建更多文件以便更好地组织它们,例如根据测试的功能来将测试分组。如前所述,*tests* 目录中的每一个文件都被编译成一个单独的 crate,这有助于创建独立的作用域,以便更接近于最终用户使用你的 crate 的方式。但这意味着,*tests* 目录中的文件的行为,和你在第七章中学习如何将代码分为模块和文件时,学到的 *src* 中的文件的行为不一样。 +随着集成测试的增加,你可能希望在 `tests` 目录创建更多文件以便更好地组织它们;例如根据测试的功能来将测试分组。如前所述,*tests* 目录中的每一个文件都被编译成一个单独的 crate,这有助于创建独立的作用域,以便更接近于最终用户使用你的 crate 的方式。但这意味着,*tests* 目录中的文件的行为,和你在第七章中学习如何将代码分为模块和文件时,学到的 *src* 中的文件的行为不一样。 -当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 [“将模块移动到其他文件”][separating-modules-into-files] 部分的步骤将它们提取到一个通用的模块中时, *tests* 目录中文件行为的不同就会凸显出来。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用: +当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章[“将模块移动到其他文件”][separating-modules-into-files]部分的步骤将它们提取到一个通用的模块中时,*tests* 目录中文件行为的不同就会凸显出来。例如,如果我们在 *tests/common.rs* 中创建一个名为 `setup` 的函数,并希望在多个测试文件的测试函数中调用它,就可以在 `setup` 中添加想要复用的代码: 文件名:tests/common.rs @@ -112,24 +111,22 @@ adder {{#include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/output.txt}} ``` -我们并不想要`common` 出现在测试结果中显示 `running 0 tests` 。我们只是希望其能被其他多个集成测试文件中调用罢了。 - -为了不让 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs* ,而不是创建 *tests/common.rs* 。现在项目目录结构看起来像这样: +我们并不想要 `common` 出现在测试结果中显示 `running 0 tests`。我们只是想与其他集成测试文件共享一些代码而已。为了不让 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs*,而不是创建 *tests/common.rs* 。现在项目目录结构看起来像这样: ```text ├── Cargo.lock ├── Cargo.toml ├── src -│   └── lib.rs +│ └── lib.rs └── tests ├── common - │   └── mod.rs + │ └── mod.rs └── integration_test.rs ``` -这是一种老的命名规范,正如第七章 [“另一种文件路径”][alt-paths] 中提到的 Rust 仍然理解它们。这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。 +这是一种老的命名规范,正如第七章[“另一种文件路径”][alt-paths]中提到的 Rust 仍然理解它们。这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。 -一旦拥有了 *tests/common/mod.rs*,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 *tests/integration_test.rs* 中调用 `setup` 函数的 `it_adds_two` 测试的例子: +一旦创建了 *tests/common/mod.rs*,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 *tests/integration_test.rs* 中调用 `setup` 函数的 `it_adds_two` 测试的示例: 文件名:tests/integration_test.rs @@ -141,15 +138,15 @@ adder #### 二进制 crate 的集成测试 -如果项目是二进制 crate 并且只包含 *src/main.rs* 而没有 *src/lib.rs*,这样就不可能在 *tests* 目录创建集成测试并使用 `extern crate` 导入 *src/main.rs* 中定义的函数。只有库 crate 才会向其他 crate 暴露了可供调用和使用的函数;二进制 crate 只意在单独运行。 +如果项目是二进制 crate 并且只包含 *src/main.rs* 而没有 *src/lib.rs*,这样就不可能在 *tests* 目录创建集成测试并也无法通过 `use` 语句将 *src/main.rs* 中定义的函数引入作用域。只有库 crate 才会向其他 crate 暴露了可供调用和使用的函数;二进制 crate 只意在单独运行。 -这就是许多 Rust 二进制项目使用一个简单的 *src/main.rs* 调用 *src/lib.rs* 中的逻辑的原因之一。因为通过这种结构,集成测试 **就可以** 通过 `extern crate` 测试库 crate 中的主要功能了,而如果这些重要的功能没有问题的话,*src/main.rs* 中的少量代码也就会正常工作且不需要测试。 +这就是许多 Rust 二进制项目使用一个简单的 *src/main.rs* 调用 *src/lib.rs* 中的逻辑的原因之一。因为通过这种结构,集成测试**就可以**通过 `use` 来测试库 crate 中的重要功能了。而如果这些重要的功能没有问题的话,*src/main.rs* 中的少量代码也就会正常工作且不需要测试。 ## 总结 -Rust 的测试功能提供了一个确保即使你改变了函数的实现方式,也能继续以期望的方式运行的途径。单元测试独立地验证库的不同部分,也能够测试私有函数实现细节。集成测试则检查多个部分是否能结合起来正确地工作,并像其他外部代码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码中不符合期望行为的逻辑 bug 仍然是很重要的。 +Rust 的测试功能提供了一个确保即使你改变了函数的实现方式,也能继续以期望的方式运行的途径。单元测试独立地验证库的不同部分,也能够测试私有函数实现细节。集成测试则检查多个部分是否能结合起来正确地工作,并像其他外部代码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免某些类型的 bug,不过测试对于减少代码中不符合期望行为的逻辑 bug 仍然是很重要的。 -让我们将本章和其他之前章节所学的知识组合起来,在下一章一起编写一个项目! +让我们将本章和其前面各章所学的知识组合起来,在下一章一起编写一个项目! [paths]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html [separating-modules-into-files]: diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md index e15a840..e2ff5d2 100644 --- a/src/ch12-00-an-io-project.md +++ b/src/ch12-00-an-io-project.md @@ -1,26 +1,25 @@ # 一个 I/O 项目:构建一个命令行程序 -> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/main/src/ch12-00-an-io-project.md) ->
-> commit 02a168ed346042f07010f8b65b4eeed623dd31d1 + + -本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 +本章既是一个目前所学的多项技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 概念。 -Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行搜索工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件路径和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。 +Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的理想语言,所以我们的项目将创建一个我们自己版本的经典命令行搜索工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件路径和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。 在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(`stderr`)而不是标准输出(`stdout`),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。 -一位 Rust 社区的成员,Andrew Gallant,已经创建了一个功能完整且非常快速的 `grep` 版本,叫做 `ripgrep`。相比之下,我们的版本将非常简单,本章将教会你一些帮助理解像 `ripgrep` 这样真实项目的背景知识。 +一位 Rust 社区的成员,Andrew Gallant,已经创建了一个功能完整且非常快速的 `grep` 版本,名为 `ripgrep`。相比之下,我们的版本将非常简单,本章将教会你一些帮助理解像 `ripgrep` 这样真实项目的背景知识。 -我们的 `grep` 项目将会结合之前所学的一些内容: +我们的 `grep` 项目将会结合之前所学的一些概念: -- 代码组织(使用 [第七章][ch7] 学习的模块) -- vector 和字符串([第八章][ch8],集合) +- 代码组织([第七章][ch7]) +- vector 和字符串([第八章][ch8]) - 错误处理([第九章][ch9]) -- 合理的使用 trait 和生命周期([第十章][ch10]) -- 测试([第十一章][ch11]) +- 合理地使用 trait 和生命周期([第十章][ch10]) +- 编写测试([第十一章][ch11]) -另外还会简要的讲到闭包、迭代器和 trait 对象,它们分别会在 [第十三章][ch13] 和 [第十八章][ch18] 中详细介绍。 +另外还会简要地讲到闭包、迭代器和 trait 对象,它们分别会在[第十三章][ch13]和[第十八章][ch18]中详细介绍。 [ch7]: ch07-00-managing-growing-projects-with-packages-crates-and-modules.html [ch8]: ch08-00-common-collections.html diff --git a/src/ch12-01-accepting-command-line-arguments.md b/src/ch12-01-accepting-command-line-arguments.md index c361a76..73fe6dc 100644 --- a/src/ch12-01-accepting-command-line-arguments.md +++ b/src/ch12-01-accepting-command-line-arguments.md @@ -1,10 +1,9 @@ ## 接受命令行参数 -> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/main/src/ch12-01-accepting-command-line-arguments.md) ->
-> commit 02a168ed346042f07010f8b65b4eeed623dd31d1 + + -一如既往使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的 `grep` 工具相区别: +一如既往使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的 `grep` 工具相区分。 ```console $ cargo new minigrep @@ -12,19 +11,19 @@ $ cargo new minigrep $ cd minigrep ``` -第一个任务是让 `minigrep` 能够接受两个命令行参数:文件路径和要搜索的字符串。也就是说我们希望能够使用 `cargo run`、要搜索的字符串和被搜索的文件的路径来运行程序,像这样: +第一个任务是让 `minigrep` 能够接受两个命令行参数:文件路径和要搜索的字符串。也就是说我们希望能够使用 `cargo run`,两个连字符来表明后面的参数是要传递给程序而不是 `cargo`,要搜索的字符串和被搜索的文件的路径来运行程序,像这样: ```console $ cargo run -- searchstring example-filename.txt ``` -现在 `cargo new` 生成的程序忽略任何传递给它的参数。[Crates.io](https://crates.io/) 上有一些现成的库可以帮助我们接受命令行参数,不过我们正在学习这些内容,让我们自己来实现一个。 +现在 `cargo new` 生成的程序无法处理传递给它的参数。[Crates.io](https://crates.io/) 上有一些现成的库可以帮助我们接受命令行参数,不过我们正在学习这一概念,就让我们自己实现这个功能。 ### 读取参数值 -为了确保 `minigrep` 能够获取传递给它的命令行参数的值,我们需要一个 Rust 标准库提供的函数 `std::env::args`。这个函数返回一个传递给程序的命令行参数的 **迭代器**(*iterator*)。我们会在 [第十三章][ch13] 全面的介绍它们。但是现在只需理解迭代器的两个细节:迭代器生成一系列的值,可以在迭代器上调用 `collect` 方法将其转换为一个集合,比如包含所有迭代器产生元素的 vector。 +为了确保 `minigrep` 能够获取传递给它的命令行参数的值,我们需要一个 Rust 标准库提供的 `std::env::args` 函数。这个函数返回一个传递给程序的命令行参数的**迭代器**(*iterator*)。我们会在[第十三章][ch13]全面介绍迭代器。但是现在只需理解迭代器的两个细节:迭代器生成一系列的值,可以在迭代器上调用 `collect` 方法将其转换为一个集合,比如包含所有迭代器产生元素的 vector。 -示例 12-1 中允许 `minigrep` 程序读取任何传递给它的命令行参数并将其收集到一个 vector 中。 +示例 12-1 中的代码允许 `minigrep` 程序读取任何传递给它的命令行参数并将其收集到一个 vector 中。 文件名:src/main.rs @@ -34,7 +33,7 @@ $ cargo run -- searchstring example-filename.txt 示例 12-1:将命令行参数收集到一个 vector 中并打印出来 -首先使用 `use` 语句来将 `std::env` 模块引入作用域以便可以使用它的 `args` 函数。注意 `std::env::args` 函数被嵌套进了两层模块中。正如 [第七章][ch7-idiomatic-use] 讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用 `std::env` 中的其他函数。这比增加了 `use std::env::args;` 后仅仅使用 `args` 调用函数要更明确一些,因为 `args` 容易被错认成一个定义于当前模块的函数。 +首先使用 `use` 语句来将 `std::env` 模块引入作用域以便可以使用它的 `args` 函数。注意 `std::env::args` 函数被嵌套进了两层模块中。正如[第七章][ch7-idiomatic-use]讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域而不是其自身。这便于我们利用 `std::env` 中的其他函数。这比增加了 `use std::env::args;` 后仅仅使用 `args` 调用函数要更明确一些,因为 `args` 容易被错认成一个定义于当前模块的函数。 > ### `args` 函数和无效的 Unicode > @@ -42,7 +41,7 @@ $ cargo run -- searchstring example-filename.txt 在 `main` 函数的第一行,我们调用了 `env::args`,并立即使用 `collect` 来创建了一个包含迭代器所有值的 vector。`collect` 可以被用来创建很多类型的集合,所以这里显式注明 `args` 的类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,然而 `collect` 是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。 -最后,我们使用调试宏打印出 vector。让我们尝试分别用两种方式(不包含参数和包含参数)运行代码: +最后,我们使用调试宏打印出 vector。让我们先在不传递任何参数的情况下运行一次代码,然后再传入两个参数运行一次: ```console {{#include ../listings/ch12-an-io-project/listing-12-01/output.txt}} @@ -74,7 +73,7 @@ $ cargo run -- searchstring example-filename.txt {{#include ../listings/ch12-an-io-project/listing-12-02/output.txt}} ``` -好的,它可以工作!我们将所需的参数值保存进了对应的变量中。之后会增加一些错误处理来应对类似用户没有提供参数的情况,不过现在我们将忽略它们并开始增加读取文件功能。 +太好了,程序正常工作!我们将所需的参数值保存进了对应的变量中。之后会增加一些错误处理来应对类似用户没有提供参数的情况。不过现在我们将忽略它们并开始增加读取文件功能。 [ch13]: ch13-00-functional-features.html [ch7-idiomatic-use]: ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#创建惯用的-use-路径