mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
check to ch12-05
This commit is contained in:
parent
8b6f370fae
commit
2eea97a90e
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch12-00-an-io-project.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit db919bc6bb9071566e9c4f05053672133eaac33e
|
||||
|
||||
本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。
|
||||
|
||||
@ -14,10 +14,18 @@ Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使
|
||||
|
||||
我们的 `grep` 项目将会结合之前所学的一些内容:
|
||||
|
||||
- 代码组织(使用第七章学习的模块)
|
||||
- vector 和字符串(第八章,集合)
|
||||
- 错误处理(第九章)
|
||||
- 合理的使用 trait 和生命周期(第十章)
|
||||
- 测试(第十一章)
|
||||
- 代码组织(使用 [第七章][ch7] 学习的模块)
|
||||
- vector 和字符串([第八章][ch8],集合)
|
||||
- 错误处理([第九章][ch9])
|
||||
- 合理的使用 trait 和生命周期([第十章][ch10])
|
||||
- 测试([第十一章][ch11])
|
||||
|
||||
另外还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在第十三章和第十七章中详细介绍。
|
||||
另外还会简要的讲到闭包、迭代器和 trait 对象,他们分别会在 [第十三章][ch13] 和 [第十七章][ch17] 中详细介绍。
|
||||
|
||||
[ch7]: ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
|
||||
[ch8]: ch08-00-common-collections.html
|
||||
[ch9]: ch09-00-error-handling.html
|
||||
[ch10]: ch10-00-generics.html
|
||||
[ch11]: ch11-00-testing.html
|
||||
[ch13]: ch13-00-functional-features.html
|
||||
[ch17]: ch17-00-oop.html
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/src/ch12-01-accepting-command-line-arguments.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
一如既往使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的 `grep` 工具相区别:
|
||||
|
||||
@ -22,7 +22,7 @@ $ cargo run searchstring example-filename.txt
|
||||
|
||||
### 读取参数值
|
||||
|
||||
为了确保 `minigrep` 能够获取传递给它的命令行参数的值,我们需要一个 Rust 标准库提供的函数,也就是 `std::env::args`。这个函数返回一个传递给程序的命令行参数的 **迭代器**(*iterator*)。我们还未讨论到迭代器(第十三章会全面的介绍他们),但是现在只需理解迭代器的两个细节:迭代器生成一系列的值,可以在迭代器上调用 `collect` 方法将其转换为一个集合,比如包含所有迭代器产生元素的 vector。
|
||||
为了确保 `minigrep` 能够获取传递给它的命令行参数的值,我们需要一个 Rust 标准库提供的函数,也就是 `std::env::args`。这个函数返回一个传递给程序的命令行参数的 **迭代器**(*iterator*)。我们会在 [第十三章][ch13] 全面的介绍它们。但是现在只需理解迭代器的两个细节:迭代器生成一系列的值,可以在迭代器上调用 `collect` 方法将其转换为一个集合,比如包含所有迭代器产生元素的 vector。
|
||||
|
||||
使用示例 12-1 中的代码来读取任何传递给 `minigrep` 的命令行参数并将其收集到一个 vector 中。
|
||||
|
||||
@ -39,7 +39,7 @@ fn main() {
|
||||
|
||||
<span class="caption">示例 12-1:将命令行参数收集到一个 vector 中并打印出来</span>
|
||||
|
||||
首先使用 `use` 语句来将 `std::env` 模块引入作用域以便可以使用它的 `args` 函数。注意 `std::env::args` 函数被嵌套进了两层模块中。正如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用 `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
|
||||
>
|
||||
@ -97,3 +97,6 @@ In file sample.txt
|
||||
```
|
||||
|
||||
好的,它可以工作!我们将所需的参数值保存进了对应的变量中。之后会增加一些错误处理来应对类似用户没有提供参数的情况,不过现在我们将忽略他们并开始增加读取文件功能。
|
||||
|
||||
[ch13]: ch13-00-functional-features.html
|
||||
[ch7-idiomatic-use]: ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#creating-idiomatic-use-paths
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/src/ch12-02-reading-a-file.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
|
||||
|
||||
现在我们要增加读取由 `filename` 命令行参数指定的文件的功能。首先,需要一个用来测试的示例文件:用来确保 `minigrep` 正常工作的最好的文件是拥有多行少量文本且有一些重复单词的文件。示例 12-3 是一首艾米莉·狄金森(Emily Dickinson)的诗,它正适合这个工作!在项目根目录创建一个文件 `poem.txt`,并输入诗 "I'm nobody! Who are you?":
|
||||
|
||||
@ -65,10 +65,10 @@ $ cargo run the poem.txt
|
||||
Searching for the
|
||||
In file poem.txt
|
||||
With text:
|
||||
I’m nobody! Who are you?
|
||||
I'm nobody! Who are you?
|
||||
Are you nobody, too?
|
||||
Then there’s a pair of us — don’t tell!
|
||||
They’d banish us, you know.
|
||||
Then there's a pair of us — don't tell!
|
||||
They'd banish us, you know.
|
||||
|
||||
How dreary to be somebody!
|
||||
How public, like a frog
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/master/src/ch12-03-improving-error-handling-and-modularity.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
为了改善我们的程序这里有四个问题需要修复,而且他们都与程序的组织方式和如何处理潜在错误有关。
|
||||
|
||||
@ -20,15 +20,16 @@
|
||||
|
||||
`main` 函数负责多个任务的组织问题在许多二进制项目中很常见。所以 Rust 社区开发出一类在 `main` 函数开始变得庞大时进行二进制程序的关注分离的指导性过程。这些过程有如下步骤:
|
||||
|
||||
1. 将程序拆分成 *main.rs* 和 *lib.rs* 并将程序的逻辑放入 *lib.rs* 中。
|
||||
2. 当命令行解析逻辑比较小时,可以保留在 *main.rs* 中。
|
||||
3. 当命令行解析开始变得复杂时,也同样将其从 *main.rs* 提取到 *lib.rs* 中。
|
||||
4. 经过这些过程之后保留在 `main` 函数中的责任应该被限制为:
|
||||
* 将程序拆分成 *main.rs* 和 *lib.rs* 并将程序的逻辑放入 *lib.rs* 中。
|
||||
* 当命令行解析逻辑比较小时,可以保留在 *main.rs* 中。
|
||||
* 当命令行解析开始变得复杂时,也同样将其从 *main.rs* 提取到 *lib.rs* 中。
|
||||
|
||||
* 使用参数值调用命令行解析逻辑
|
||||
* 设置任何其他的配置
|
||||
* 调用 *lib.rs* 中的 `run` 函数
|
||||
* 如果 `run` 返回错误,则处理这个错误
|
||||
经过这些过程之后保留在 `main` 函数中的责任应该被限制为:
|
||||
|
||||
* 使用参数值调用命令行解析逻辑
|
||||
* 设置任何其他的配置
|
||||
* 调用 *lib.rs* 中的 `run` 函数
|
||||
* 如果 `run` 返回错误,则处理这个错误
|
||||
|
||||
这个模式的一切就是为了关注分离:*main.rs* 处理程序运行,而 *lib.rs* 处理所有的真正的任务逻辑。因为不能直接测试 `main` 函数,这个结构通过将所有的程序逻辑移动到 *lib.rs* 的函数中使得我们可以测试他们。仅仅保留在 *main.rs* 中的代码将足够小以便阅读就可以验证其正确性。让我们遵循这些步骤来重构程序。
|
||||
|
||||
@ -69,7 +70,7 @@ fn parse_config(args: &[String]) -> (&str, &str) {
|
||||
|
||||
> 注意:一些同学将这种在复杂类型更为合适的场景下使用基本类型的反模式称为 **基本类型偏执**(*primitive obsession*)。
|
||||
|
||||
示例 12-6 展示了新定义的结构体 `Config`,它有字段 `query` 和 `filename`。我们也改变了 `parse_config` 函数来返回一个 `Config` 结构体的实例,并更新 `main` 来使用结构体字段而不是单独的变量:
|
||||
示例 12-6 展示了 `parse_config` 函数的改进。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -106,6 +107,7 @@ fn parse_config(args: &[String]) -> Config {
|
||||
|
||||
<span class="caption">示例 12-6:重构 `parse_config` 返回一个 `Config` 结构体实例</span>
|
||||
|
||||
新定义的结构体 `Config` 中包含字段 `query` 和 `filename`。
|
||||
`parse_config` 的签名表明它现在返回一个 `Config` 值。在之前的 `parse_config` 函数体中,我们返回了引用 `args` 中 `String` 值的字符串 slice,现在我们定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
|
||||
|
||||
还有许多不同的方式可以处理 `String` 的数据,而最简单但有些不太高效的方式是调用这些值的 `clone` 方法。这会生成 `Config` 实例可以拥有的数据的完整拷贝,不过会比储存字符串数据的引用消耗更多的时间和内存。不过拷贝数据使得代码显得更加直白因为无需管理引用的生命周期,所以在这种情况下牺牲一小部分性能来换取简洁性的取舍是值得的。
|
||||
@ -118,7 +120,7 @@ fn parse_config(args: &[String]) -> Config {
|
||||
|
||||
现在代码更明确的表现了我们的意图,`query` 和 `filename` 是相关联的并且他们的目的是配置程序如何工作。任何使用这些值的代码就知道在 `config` 实例中对应目的的字段名中寻找他们。
|
||||
|
||||
### 创建一个 `Config` 构造函数
|
||||
### 创建一个 `Config` 的构造函数
|
||||
|
||||
目前为止,我们将负责解析命令行参数的逻辑从 `main` 提取到了 `parse_config` 函数中,这有助于我们看清值 `query` 和 `filename` 是相互关联的并应该在代码中表现这种关系。接着我们增加了 `Config` 结构体来描述 `query` 和 `filename` 的相关性,并能够从 `parse_config` 函数中将这些值的名称作为结构体字段名称返回。
|
||||
|
||||
@ -168,7 +170,7 @@ $ cargo run
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||
Running `target/debug/minigrep`
|
||||
thread 'main' panicked at 'index out of bounds: the len is 1
|
||||
but the index is 1', src/main.rs:29:21
|
||||
but the index is 1', src/main.rs:25:21
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
```
|
||||
|
||||
@ -191,8 +193,7 @@ fn new(args: &[String]) -> Config {
|
||||
|
||||
<span class="caption">示例 12-8:增加一个参数数量检查</span>
|
||||
|
||||
这类似于示例 9-9 中的 `Guess::new` 函数,那里如果 `value` 参数超出了有效值的范围就调用 `panic!`。不同于检查值的范围,这里检查 `args` 的长度至少是 `3`,而函数的剩余部分则可以在假设这个条件成立的基础上运行。如果
|
||||
`args` 少于 3 个项,则这个条件将为真,并调用 `panic!` 立即终止程序。
|
||||
这类似于 [示例 9-10 中的 `Guess::new` 函数][ch9-custom-types],那里如果 `value` 参数超出了有效值的范围就调用 `panic!`。不同于检查值的范围,这里检查 `args` 的长度至少是 `3`,而函数的剩余部分则可以在假设这个条件成立的基础上运行。如果 `args` 少于 3 个项,则这个条件将为真,并调用 `panic!` 立即终止程序。
|
||||
|
||||
有了 `new` 中这几行额外的代码,再次不带任何参数运行程序并看看现在错误看起来像什么:
|
||||
|
||||
@ -201,11 +202,11 @@ $ cargo run
|
||||
Compiling minigrep v0.1.0 (file:///projects/minigrep)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||
Running `target/debug/minigrep`
|
||||
thread 'main' panicked at 'not enough arguments', src/main.rs:30:12
|
||||
thread 'main' panicked at 'not enough arguments', src/main.rs:26:13
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
```
|
||||
|
||||
这个输出就好多了,现在有了一个合理的错误信息。然而,还是有一堆额外的信息我们不希望提供给用户。所以在这里使用示例 9-9 中的技术可能不是最好的;正如第九章所讲到的一样,`panic!` 的调用更趋向于程序上的问题而不是使用上的问题。相反我们可以使用第九章学习的另一个技术 —— 返回一个可以表明成功或错误的 `Result`。
|
||||
这个输出就好多了,现在有了一个合理的错误信息。然而,还是有一堆额外的信息我们不希望提供给用户。所以在这里使用示例 9-9 中的技术可能不是最好的;正如 [第九章][ch9-error-guidelines] 所讲到的一样,`panic!` 的调用更趋向于程序上的问题而不是使用上的问题。相反我们可以使用第九章学习的另一个技术 —— 返回一个可以表明成功或错误的 [`Result`][ch9-result]。
|
||||
|
||||
#### 从 `new` 中返回 `Result` 而不是调用 `panic!`
|
||||
|
||||
@ -260,7 +261,7 @@ fn main() {
|
||||
|
||||
<span class="caption">示例 12-10:如果新建 `Config` 失败则使用错误码退出</span>
|
||||
|
||||
在上面的示例中,使用了一个之前没有涉及到的方法:`unwrap_or_else`,它定义于标准库的 `Result<T, E>` 上。使用 `unwrap_or_else` 可以进行一些自定义的非 `panic!` 的错误处理。当 `Result` 是 `Ok` 时,这个方法的行为类似于 `unwrap`:它返回 `Ok` 内部封装的值。然而,当其值是 `Err` 时,该方法会调用一个 **闭包**(*closure*),也就是一个我们定义的作为参数传递给 `unwrap_or_else` 的匿名函数。第十三章会更详细的介绍闭包。现在你需要理解的是 `unwrap_or_else` 会将 `Err` 的内部值,也就是示例 12-9 中增加的 `not enough arguments` 静态字符串的情况,传递给闭包中位于两道竖线间的参数 `err`。闭包中的代码在其运行时可以使用这个 `err` 值。
|
||||
在上面的示例中,使用了一个之前没有涉及到的方法:`unwrap_or_else`,它定义于标准库的 `Result<T, E>` 上。使用 `unwrap_or_else` 可以进行一些自定义的非 `panic!` 的错误处理。当 `Result` 是 `Ok` 时,这个方法的行为类似于 `unwrap`:它返回 `Ok` 内部封装的值。然而,当其值是 `Err` 时,该方法会调用一个 **闭包**(*closure*),也就是一个我们定义的作为参数传递给 `unwrap_or_else` 的匿名函数。[第十三章][ch13] 会更详细的介绍闭包。现在你需要理解的是 `unwrap_or_else` 会将 `Err` 的内部值,也就是示例 12-9 中增加的 `not enough arguments` 静态字符串的情况,传递给闭包中位于两道竖线间的参数 `err`。闭包中的代码在其运行时可以使用这个 `err` 值。
|
||||
|
||||
我们新增了一个 `use` 行来从标准库中导入 `process`。在错误的情况闭包中将被运行的代码只有两行:我们打印出了 `err` 值,接着调用了 `std::process::exit`。`process::exit` 会立即停止程序并将传递给它的数字作为退出状态码。这类似于示例 12-8 中使用的基于 `panic!` 的错误处理,除了不会再得到所有的额外输出了。让我们试试:
|
||||
|
||||
@ -276,7 +277,7 @@ Problem parsing arguments: not enough arguments
|
||||
|
||||
### 从 `main` 提取逻辑
|
||||
|
||||
现在我们完成了配置解析的重构:让我们转向程序的逻辑。正如 “二进制项目的关注分离” 部分所展开的讨论,我们将提取一个叫做 `run` 的函数来存放目前 `main `函数中不属于设置配置或处理错误的所有逻辑。一旦完成这些,`main` 函数将简明的足以通过观察来验证,而我们将能够为所有其他逻辑编写测试。
|
||||
现在我们完成了配置解析的重构:让我们转向程序的逻辑。正如 [“二进制项目的关注分离”](#separation-of-concerns-for-binary-projects) 部分所展开的讨论,我们将提取一个叫做 `run` 的函数来存放目前 `main `函数中不属于设置配置或处理错误的所有逻辑。一旦完成这些,`main` 函数将简明的足以通过观察来验证,而我们将能够为所有其他逻辑编写测试。
|
||||
|
||||
示例 12-11 展示了提取出来的 `run` 函数。目前我们只进行小的增量式的提取函数的改进。我们仍将在 *src/main.rs* 中定义这个函数:
|
||||
|
||||
@ -294,7 +295,7 @@ fn main() {
|
||||
|
||||
fn run(config: Config) {
|
||||
let contents = fs::read_to_string(config.filename)
|
||||
.expect("something went wrong reading the file");
|
||||
.expect("Something went wrong reading the file");
|
||||
|
||||
println!("With text:\n{}", contents);
|
||||
}
|
||||
@ -330,21 +331,23 @@ fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
这里我们做出了三个明显的修改。首先,将 `run` 函数的返回类型变为 `Result<(), Box<dyn Error>>`。之前这个函数返回 unit 类型 `()`,现在它仍然保持作为 `Ok` 时的返回值。
|
||||
|
||||
对于错误类型,使用了 **trait 对象** `Box<dyn Error>`(在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。第十七章会涉及 trait 对象。目前只需知道 `Box<dyn Error>` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 `dyn`,它是 “动态的”(“dynamic”)的缩写。
|
||||
对于错误类型,使用了 **trait 对象** `Box<dyn Error>`(在开头使用了 `use` 语句将 `std::error::Error` 引入作用域)。[第十七章][ch17] 会涉及 trait 对象。目前只需知道 `Box<dyn Error>` 意味着函数会返回实现了 `Error` trait 的类型,不过无需指定具体将会返回的值的类型。这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 `dyn`,它是 “动态的”(“dynamic”)的缩写。
|
||||
|
||||
第二个改变是去掉了 `expect` 调用并替换为第九章讲到的 `?`。不同于遇到错误就 `panic!`,`?` 会从函数中返回错误值并让调用者来处理它。
|
||||
第二个改变是去掉了 `expect` 调用并替换为 [第九章][ch9-question-mark] 讲到的 `?`。不同于遇到错误就 `panic!`,`?` 会从函数中返回错误值并让调用者来处理它。
|
||||
|
||||
第三个修改是现在成功时这个函数会返回一个 `Ok` 值。因为 `run` 函数签名中声明成功类型返回值是 `()`,这意味着需要将 unit 类型值包装进 `Ok` 值中。`Ok(())` 一开始看起来有点奇怪,不过这样使用 `()` 是表明我们调用 `run` 只是为了它的副作用的惯用方式;它并没有返回什么有意义的值。
|
||||
|
||||
上述代码能够编译,不过会有一个警告:
|
||||
|
||||
```text
|
||||
warning: unused `std::result::Result` which must be used
|
||||
--> src/main.rs:18:5
|
||||
warning: unused `std::result::Result` that must be used
|
||||
--> src/main.rs:17:5
|
||||
|
|
||||
18 | run(config);
|
||||
17 | run(config);
|
||||
| ^^^^^^^^^^^^
|
||||
= note: #[warn(unused_must_use)] on by default
|
||||
|
|
||||
= note: #[warn(unused_must_use)] on by default
|
||||
= note: this `Result` may be an `Err` variant, which should be handled
|
||||
```
|
||||
|
||||
Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在一个错误。虽然我们没有检查这里是否有一个错误,而编译器提醒我们这里应该有一些错误处理代码!现在就让我们修正这个问题。
|
||||
@ -421,7 +424,6 @@ pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
use minigrep;
|
||||
use minigrep::Config;
|
||||
|
||||
fn main() {
|
||||
@ -439,3 +441,11 @@ fn main() {
|
||||
哇哦!这可有很多的工作,不过我们为将来的成功打下了基础。现在处理错误将更容易,同时代码也更加模块化。从现在开始几乎所有的工作都将在 *src/lib.rs* 中进行。
|
||||
|
||||
让我们利用这些新创建的模块的优势来进行一些在旧代码中难以展开的工作,这些工作在新代码中非常容易实现,那就是:编写测试!
|
||||
|
||||
[the-static-lifetime]: ch10-03-lifetime-syntax.html#the-static-lifetime
|
||||
[ch13]: ch13-00-functional-features.html
|
||||
[ch9-custom-types]: ch09-03-to-panic-or-not-to-panic.html#creating-custom-types-for-validation
|
||||
[ch9-error-guidelines]: ch09-03-to-panic-or-not-to-panic.html#guidelines-for-error-handling
|
||||
[ch9-result]: ch09-02-recoverable-errors-with-result.html
|
||||
[ch17]: ch17-00-oop.html
|
||||
[ch9-question-mark]: ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/master/src/ch12-04-testing-the-librarys-functionality.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit 0ca4b88f75f8579de87adc2ad36d340709f5ccad
|
||||
|
||||
现在我们将逻辑提取到了 *src/lib.rs* 并将所有的参数解析和错误处理留在了 *src/main.rs* 中,为代码的核心功能编写测试将更加容易。我们可以直接使用多种参数调用函数并检查返回值而无需从命令行运行二进制文件了。如果你愿意的话,请自行为 `Config::new` 和 `run` 函数的功能编写一些测试。
|
||||
|
||||
@ -19,12 +19,12 @@
|
||||
|
||||
### 编写失败测试
|
||||
|
||||
去掉 *src/lib.rs* 和 *src/main.rs* 中用于检查程序行为的 `println!` 语句,因为不再真正需要他们了。接着我们会像第十一章那样增加一个 `test` 模块和一个测试函数。测试函数指定了 `search` 函数期望拥有的行为:它会获取一个需要查询的字符串和用来查询的文本,并只会返回包含请求的文本行。示例 12-15 展示了这个测试,它还不能编译:
|
||||
去掉 *src/lib.rs* 和 *src/main.rs* 中用于检查程序行为的 `println!` 语句,因为不再真正需要他们了。接着我们会像 [第十一章][ch11-anatomy] 那样增加一个 `test` 模块和一个测试函数。测试函数指定了 `search` 函数期望拥有的行为:它会获取一个需要查询的字符串和用来查询的文本,并只会返回包含请求的文本行。示例 12-15 展示了这个测试,它还不能编译:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# vec![]
|
||||
# }
|
||||
#
|
||||
@ -57,14 +57,14 @@ Pick three.";
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
vec![]
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 12-16:刚好足够使测试通过编译的 `search` 函数定义</span>
|
||||
|
||||
注意需要在 `search` 的签名中定义一个显式生命周期 `'a` 并用于 `contents` 参数和返回值。回忆一下第十章中讲到生命周期参数指定哪个参数的生命周期与返回值的生命周期相关联。在这个例子中,我们表明返回的 vector 中应该包含引用参数 `contents`(而不是参数`query`) slice 的字符串 slice。
|
||||
注意需要在 `search` 的签名中定义一个显式生命周期 `'a` 并用于 `contents` 参数和返回值。回忆一下 [第十章][ch10-lifetimes] 中讲到生命周期参数指定哪个参数的生命周期与返回值的生命周期相关联。在这个例子中,我们表明返回的 vector 中应该包含引用参数 `contents`(而不是参数`query`) slice 的字符串 slice。
|
||||
|
||||
换句话说,我们告诉 Rust 函数 `search` 返回的数据将与 `search` 函数中的参数 `contents` 的数据存在的一样久。这是非常重要的!为了使这个引用有效那么 **被** slice 引用的数据也需要保持有效;如果编译器认为我们是在创建 `query` 而不是 `contents` 的字符串 slice,那么安全检查将是不正确的。
|
||||
|
||||
@ -74,7 +74,7 @@ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
error[E0106]: missing lifetime specifier
|
||||
--> src/lib.rs:5:51
|
||||
|
|
||||
5 | fn search(query: &str, contents: &str) -> Vec<&str> {
|
||||
5 | pub fn search(query: &str, contents: &str) -> Vec<&str> {
|
||||
| ^ expected lifetime
|
||||
parameter
|
||||
|
|
||||
@ -84,7 +84,7 @@ parameter
|
||||
|
||||
Rust 不可能知道我们需要的是哪一个参数,所以需要告诉它。因为参数 `contents` 包含了所有的文本而且我们希望返回匹配的那部分文本,所以我们知道 `contents` 是应该要使用生命周期语法来与返回值相关联的参数。
|
||||
|
||||
其他语言中并不需要你在函数签名中将参数与返回值相关联。所以这么做可能仍然感觉有些陌生,随着时间的推移这将会变得越来越容易。你可能想要将这个例子与第十章中生命 “生命周期与引用有效性” 部分做对比。
|
||||
其他语言中并不需要你在函数签名中将参数与返回值相关联。所以这么做可能仍然感觉有些陌生,随着时间的推移这将会变得越来越容易。你可能想要将这个例子与第十章中生命 [“生命周期与引用有效性”][validating-references-with-lifetimes] 部分做对比。
|
||||
|
||||
现在运行测试:
|
||||
|
||||
@ -137,7 +137,7 @@ Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
for line in contents.lines() {
|
||||
// do something with line
|
||||
}
|
||||
@ -146,7 +146,7 @@ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
|
||||
<span class="caption">示例 12-17:遍历 `contents` 的每一行</span>
|
||||
|
||||
`lines` 方法返回一个迭代器。第十三章会深入了解迭代器,不过我们已经在示例 3-5 中见过使用迭代器的方法了,在那里使用了一个 `for` 循环和迭代器在一个集合的每一项上运行了一些代码。
|
||||
`lines` 方法返回一个迭代器。[第十三章][ch13-iterators] 会深入了解迭代器,不过我们已经在 [示例 3-5][ch3-iter] 中见过使用迭代器的方法了,在那里使用了一个 `for` 循环和迭代器在一个集合的每一项上运行了一些代码。
|
||||
|
||||
#### 用查询字符串搜索每一行
|
||||
|
||||
@ -155,7 +155,7 @@ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
for line in contents.lines() {
|
||||
if line.contains(query) {
|
||||
// do something with line
|
||||
@ -173,7 +173,7 @@ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for line in contents.lines() {
|
||||
@ -255,3 +255,10 @@ $ cargo run monomorphization poem.txt
|
||||
非常好!我们创建了一个属于自己的迷你版经典工具,并学习了很多如何组织程序的知识。我们还学习了一些文件输入输出、生命周期、测试和命令行解析的内容。
|
||||
|
||||
为了使这个项目更丰满,我们将简要的展示如何处理环境变量和打印到标准错误,这两者在编写命令行程序时都很有用。
|
||||
|
||||
[validating-references-with-lifetimes]:
|
||||
ch10-03-lifetime-syntax.html#validating-references-with-lifetimes
|
||||
[ch11-anatomy]: ch11-01-writing-tests.html#the-anatomy-of-a-test-function
|
||||
[ch10-lifetimes]: ch10-03-lifetime-syntax.html
|
||||
[ch3-iter]: ch03-05-control-flow.html#looping-through-a-collection-with-for
|
||||
[ch13-iterators]: ch13-02-iterators.html
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/master/src/ch12-05-working-with-environment-variables.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
|
||||
|
||||
我们将增加一个额外的功能来改进 `minigrep`:用户可以通过设置环境变量来设置搜索是否是大小写敏感的 。当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
|
||||
|
||||
@ -62,7 +62,7 @@ Trust me.";
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
let query = query.to_lowercase();
|
||||
let mut results = Vec::new();
|
||||
|
||||
@ -115,11 +115,11 @@ pub struct Config {
|
||||
# use std::fs::{self, File};
|
||||
# use std::io::prelude::*;
|
||||
#
|
||||
# fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# vec![]
|
||||
# }
|
||||
#
|
||||
# fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||
# vec![]
|
||||
# }
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user