error[E0106]: missing lifetime specifier
- --> src\lib.rs:37:46
- |
-37 | fn grep(search: &str, contents: &str) -> Vec<&str> {
- | ^ expected lifetime parameter
- |
- = help: this function's return type contains a borrowed value, but the
- signature does not say whether it is borrowed from `search` or
- `contents`
+ --> src/lib.rs:5:47
+ |
+5 | fn search(query: &str, contents: &str) -> Vec<&str> {
+ | ^ expected lifetime parameter
+ |
+ = help: this function's return type contains a borrowed value, but the
+ signature does not say whether it is borrowed from `query` or `contents`
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
// do something with line
}
}
}
+
Listing 12-18: Adding functionality to see if the line
+contains the string in query
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
results.push(line);
}
}
@@ -206,10 +221,10 @@ error: test failed
results
}
-
Listing 12-15: Fully functioning implementation of the
-grep function
+
Listing 12-19: Storing the lines that match so that we
+can return them
-
尝试运行一下:
+
现在search函数应该返回只包含query的那些行,而测试应该会通过。让我们运行测试:
$ cargo test
running 1 test
test test::one_result ... ok
@@ -228,8 +243,20 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
$ cargo run frog poem.txt
Compiling greprs v0.1.0 (file:///projects/greprs)
Finished debug [unoptimized + debuginfo] target(s) in 0.38 secs
- Running `target\debug\greprs.exe the poem.txt`
-Then there's a pair of us - don't tell!
-To tell your name the livelong day
-
-$ cargo run a poem.txt
- Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe a poem.txt`
-I'm nobody! Who are you?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-How dreary to be somebody!
+ Running `target/debug/greprs frog poem.txt`
How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
$ cargo run the poem.txt
+ Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+ Running `target/debug/greprs the poem.txt`
+Then there's a pair of us — don't tell!
+To tell your name the livelong day
+
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+ let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
- if line.to_lowercase().contains(&search) {
+ if line.to_lowercase().contains(&query) {
results.push(line);
}
}
@@ -127,14 +141,24 @@ Trust me.";
results
}
-
Listing 12-16: Implementing a grep_case_insensitive
-function by changing the search string and the lines of the contents to
-lowercase before comparing them
+
Listing 12-21: Defining the search_case_insensitive
+function to lowercase both the query and the line before comparing them
$ cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
$ CASE_INSENSITIVE=1 cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
<!-- The code example I want to reference did not have a listing number; it's
+the one with SpreadsheetCell. I will go back and add Listing 8-1 next time I
+get Chapter 8 for editing. /Carol -->
+
error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
+ -->
+ |
+ 4 | Box::new(String::from("Hi")),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not
+ implemented for `std::string::String`
+ |
+ = note: required for the cast to the object type `Draw`
+
Not all traits can be made into trait objects; only object safe traits can. A
+trait is object safe as long as both of the following are true:
+
+
The trait does not require Self to be Sized
+
All of the trait's methods are object safe.
+
+
Self is a keyword that is an alias for the type that we're implementing
+traits or methods on. Sized is a marker trait like the Send and Sync
+traits that we talked about in Chapter 16. Sized is automatically implemented
+on types that have a known size at compile time, such as i32 and references.
+Types that do not have a known size include slices ([T]) and trait objects.
+
Sized is an implicit trait bound on all generic type parameters by default.
+Most useful operations in Rust require a type to be Sized, so making Sized
+a default requirement on trait bounds means we don't have to write T: Sized
+with most every use of generics. If we want to be able to use a trait on
+slices, however, we need to opt out of the Sized trait bound, and we can do
+that by specifying T: ?Sized as a trait bound.
+
Traits have a default bound of Self: ?Sized, which means that they can be
+implemented on types that may or may not be Sized. If we create a trait Foo
+that opts out of the Self: ?Sized bound, that would look like the following:
+
trait Foo: Sized {
+ fn some_method(&self);
+}
+
+
The trait Sized is now a super trait of trait Foo, which means trait
+Foo requires types that implement Foo (that is, Self) to be Sized.
+We're going to talk about super traits in more detail in Chapter 19.
+
The reason a trait like Foo that requires Self to be Sized is not allowed
+to be a trait object is that it would be impossible to implement the trait
+Foo for the trait object Foo: trait objects aren't sized, but Foo
+requires Self to be Sized. A type can't be both sized and unsized at the
+same time!
+
For the second object safety requirement that says all of a trait's methods
+must be object safe, a method is object safe if either:
+
+
It requires Self to be Sized or
+
It meets all three of the following:
+
+
It must not have any generic type parameters
+
Its first argument must be of type Self or a type that dereferences to
+the Self type (that is, it must be a method rather than an associated
+function and have self, &self, or &mut self as the first argument)
+
It must not use Self anywhere else in the signature except for the
+first argument
+
+
+
+
Those rules are a bit formal, but think of it this way: if your method requires
+the concrete Self type somewhere in its signature, but an object forgets the
+exact type that it is, there's no way that the method can use the original
+concrete type that it's forgotten. Same with generic type parameters that are
+filled in with concrete type parameters when the trait is used: the concrete
+types become part of the type that implements the trait. When the type is
+erased by the use of a trait object, there's no way to know what types to fill
+in the generic type parameters with.
+
An example of a trait whose methods are not object safe is the standard
+library's Clone trait. The signature for the clone method in the Clone
+trait looks like this:
+
pub trait Clone {
+ fn clone(&self) -> Self;
+}
+
+
String implements the Clone trait, and when we call the clone method on
+an instance of String we get back an instance of String. Similarly, if we
+call clone on an instance of Vec, we get back an instance of Vec. The
+signature of clone needs to know what type will stand in for Self, since
+that's the return type.
+
If we try to implement Clone on a trait like the Draw trait from Listing
+17-3, we wouldn't know whether Self would end up being a Button, a
+SelectBox, or some other type that will implement the Draw trait in the
+future.
+
The compiler will tell you if you're trying to do something that violates the
+rules of object safety in regards to trait objects. For example, if we had
+tried to implement the Screen struct in Listing 17-4 to hold types that
+implement the Clone trait instead of the Draw trait, like this:
error[E0038]: the trait `std::clone::Clone` cannot be made into an object
+ -->
+ |
+2 | pub components: Vec<Box<Clone>>,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be
+ made into an object
+ |
+ = note: the trait cannot require that `Self : Sized`
+
# #[derive(Debug)]
# struct Rectangle {
@@ -2734,7 +2734,7 @@ crashes, which have probably caused a billion dollars of pain and damage in
the last forty years.
error[E0106]: missing lifetime specifier
- --> src\lib.rs:37:46
- |
-37 | fn grep(search: &str, contents: &str) -> Vec<&str> {
- | ^ expected lifetime parameter
- |
- = help: this function's return type contains a borrowed value, but the
- signature does not say whether it is borrowed from `search` or
- `contents`
+ --> src/lib.rs:5:47
+ |
+5 | fn search(query: &str, contents: &str) -> Vec<&str> {
+ | ^ expected lifetime parameter
+ |
+ = help: this function's return type contains a borrowed value, but the
+ signature does not say whether it is borrowed from `query` or `contents`
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
// do something with line
}
}
}
+
Listing 12-18: Adding functionality to see if the line
+contains the string in query
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
results.push(line);
}
}
@@ -7045,10 +7060,10 @@ error: test failed
results
}
-
Listing 12-15: Fully functioning implementation of the
-grep function
+
Listing 12-19: Storing the lines that match so that we
+can return them
-
尝试运行一下:
+
现在search函数应该返回只包含query的那些行,而测试应该会通过。让我们运行测试:
$ cargo test
running 1 test
test test::one_result ... ok
@@ -7067,8 +7082,20 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
$ cargo run frog poem.txt
Compiling greprs v0.1.0 (file:///projects/greprs)
Finished debug [unoptimized + debuginfo] target(s) in 0.38 secs
- Running `target\debug\greprs.exe the poem.txt`
-Then there's a pair of us - don't tell!
-To tell your name the livelong day
-
-$ cargo run a poem.txt
- Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe a poem.txt`
-I'm nobody! Who are you?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-How dreary to be somebody!
+ Running `target/debug/greprs frog poem.txt`
How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
$ cargo run the poem.txt
+ Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+ Running `target/debug/greprs the poem.txt`
+Then there's a pair of us — don't tell!
+To tell your name the livelong day
+
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+ let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
- if line.to_lowercase().contains(&search) {
+ if line.to_lowercase().contains(&query) {
results.push(line);
}
}
@@ -7164,14 +7207,24 @@ Trust me.";
results
}
-
Listing 12-16: Implementing a grep_case_insensitive
-function by changing the search string and the lines of the contents to
-lowercase before comparing them
+
Listing 12-21: Defining the search_case_insensitive
+function to lowercase both the query and the line before comparing them
$ cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
$ CASE_INSENSITIVE=1 cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
<!-- The code example I want to reference did not have a listing number; it's
+the one with SpreadsheetCell. I will go back and add Listing 8-1 next time I
+get Chapter 8 for editing. /Carol -->
+
error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
+ -->
+ |
+ 4 | Box::new(String::from("Hi")),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not
+ implemented for `std::string::String`
+ |
+ = note: required for the cast to the object type `Draw`
+
Not all traits can be made into trait objects; only object safe traits can. A
+trait is object safe as long as both of the following are true:
+
+
The trait does not require Self to be Sized
+
All of the trait's methods are object safe.
+
+
Self is a keyword that is an alias for the type that we're implementing
+traits or methods on. Sized is a marker trait like the Send and Sync
+traits that we talked about in Chapter 16. Sized is automatically implemented
+on types that have a known size at compile time, such as i32 and references.
+Types that do not have a known size include slices ([T]) and trait objects.
+
Sized is an implicit trait bound on all generic type parameters by default.
+Most useful operations in Rust require a type to be Sized, so making Sized
+a default requirement on trait bounds means we don't have to write T: Sized
+with most every use of generics. If we want to be able to use a trait on
+slices, however, we need to opt out of the Sized trait bound, and we can do
+that by specifying T: ?Sized as a trait bound.
+
Traits have a default bound of Self: ?Sized, which means that they can be
+implemented on types that may or may not be Sized. If we create a trait Foo
+that opts out of the Self: ?Sized bound, that would look like the following:
+
trait Foo: Sized {
+ fn some_method(&self);
+}
+
+
The trait Sized is now a super trait of trait Foo, which means trait
+Foo requires types that implement Foo (that is, Self) to be Sized.
+We're going to talk about super traits in more detail in Chapter 19.
+
The reason a trait like Foo that requires Self to be Sized is not allowed
+to be a trait object is that it would be impossible to implement the trait
+Foo for the trait object Foo: trait objects aren't sized, but Foo
+requires Self to be Sized. A type can't be both sized and unsized at the
+same time!
+
For the second object safety requirement that says all of a trait's methods
+must be object safe, a method is object safe if either:
+
+
It requires Self to be Sized or
+
It meets all three of the following:
+
+
It must not have any generic type parameters
+
Its first argument must be of type Self or a type that dereferences to
+the Self type (that is, it must be a method rather than an associated
+function and have self, &self, or &mut self as the first argument)
+
It must not use Self anywhere else in the signature except for the
+first argument
+
+
+
+
Those rules are a bit formal, but think of it this way: if your method requires
+the concrete Self type somewhere in its signature, but an object forgets the
+exact type that it is, there's no way that the method can use the original
+concrete type that it's forgotten. Same with generic type parameters that are
+filled in with concrete type parameters when the trait is used: the concrete
+types become part of the type that implements the trait. When the type is
+erased by the use of a trait object, there's no way to know what types to fill
+in the generic type parameters with.
+
An example of a trait whose methods are not object safe is the standard
+library's Clone trait. The signature for the clone method in the Clone
+trait looks like this:
+
pub trait Clone {
+ fn clone(&self) -> Self;
+}
+
+
String implements the Clone trait, and when we call the clone method on
+an instance of String we get back an instance of String. Similarly, if we
+call clone on an instance of Vec, we get back an instance of Vec. The
+signature of clone needs to know what type will stand in for Self, since
+that's the return type.
+
If we try to implement Clone on a trait like the Draw trait from Listing
+17-3, we wouldn't know whether Self would end up being a Button, a
+SelectBox, or some other type that will implement the Draw trait in the
+future.
+
The compiler will tell you if you're trying to do something that violates the
+rules of object safety in regards to trait objects. For example, if we had
+tried to implement the Screen struct in Listing 17-4 to hold types that
+implement the Clone trait instead of the Draw trait, like this:
error[E0038]: the trait `std::clone::Clone` cannot be made into an object
+ -->
+ |
+2 | pub components: Vec<Box<Clone>>,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be
+ made into an object
+ |
+ = note: the trait cannot require that `Self : Sized`
+
+
diff --git a/src/ch12-04-testing-the-librarys-functionality.md b/src/ch12-04-testing-the-librarys-functionality.md
index 0132c74..018d75d 100644
--- a/src/ch12-04-testing-the-librarys-functionality.md
+++ b/src/ch12-04-testing-the-librarys-functionality.md
@@ -2,48 +2,39 @@
> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-04-testing-the-librarys-functionality.md)
>
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+> commit b8e4fcbf289b82c12121b282747ce05180afb1fb
-现在为项目的核心功能编写测试将更加容易,因为我们将逻辑提取到了 *src/lib.rs* 中并将参数解析和错误处理都留在了 *src/main.rs* 里。现在我们可以直接使用多种参数调用代码并检查返回值而不用从命令行运行二进制文件了。
+现在我们将逻辑提取到了 *src/lib.rs* 并将所有的参数解析和错误处理留在了 *src/main.rs* 中,为代码的核心功能编写测试将更加容易。我们可以直接使用多种参数调用函数并检查返回值而无需从命令行运行二进制文件了。
-我们将要编写的是一个叫做`grep`的函数,它获取要搜索的项以及文本并产生一个搜索结果列表。让我们从`run`中去掉那行`println!`(也去掉 *src/main.rs* 中的,因为再也不需要他们了),并使用之前收集的选项来调用新的`grep`函数。眼下我们只增加一个空的实现,和指定`grep`期望行为的测试。当然,这个测试对于空的实现来说是会失败的,不过可以确保代码是可以编译的并得到期望的错误信息。列表 12-14 展示了这些修改:
+在这一部分,我们将遵循测试驱动开发(Test Driven Development, TTD)的模式。这是一个软件开发技术,它遵循如下步骤:
+
+1. 编写一个会失败的测试,并运行它以确保其因为你期望的原因失败。
+2. 编写或修改刚好足够的代码来使得新的测试通过。
+3. 重构刚刚增加或修改的代码,并确保测试仍然能通过。
+4. 重复上述步骤!
+
+这只是众多编写软件的方法之一,不过 TDD 有助于驱动代码的设计。在编写能使测试通过的代码之前编写测测试有助于在开发过程中保持高测试覆盖率。
+
+我们将测试驱动实现`greprs`实际在文件内容中搜索查询字符串并返回匹配的行列表的部分。我们将在一个叫做`search`的函数中增加这些功能。
+
+### 编写失败测试
+
+首先,去掉 *src/lib.rs* 和 *src/main.rs* 中的`println!`语句,因为不再真的需要他们了。接着我们会像第十一章那样增加一个`test`模块和一个测试函数。测试函数指定了我们希望`search`函数拥有的行为:它会获取一个需要查询的字符串和用来查询的文本。列表 12-15 展示了这个测试:
Filename: src/lib.rs
```rust
-# use std::error::Error;
-# use std::fs::File;
-# use std::io::prelude::*;
-#
-# pub struct Config {
-# pub search: String,
-# pub filename: String,
+# fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+# vec![]
# }
#
-// ...snip...
-
-fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
- vec![]
-}
-
-pub fn run(config: Config) -> Result<(), Box>{
- let mut f = File::open(config.filename)?;
-
- let mut contents = String::new();
- f.read_to_string(&mut contents)?;
-
- grep(&config.search, &contents);
-
- Ok(())
-}
-
#[cfg(test)]
mod test {
- use grep;
+ use super::*;
#[test]
fn one_result() {
- let search = "duct";
+ let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
@@ -51,36 +42,54 @@ Pick three.";
assert_eq!(
vec!["safe, fast, productive."],
- grep(search, contents)
+ search(query, contents)
);
}
}
```
-Listing 12-14: Creating a function where our logic will
-go and a failing test for that function
+Listing 12-15: Creating a failing test for the `search`
+function we wish we had
+
+这里选择使用 "duct" 作为这个测试中需要搜索的字符串。用来搜索的文本有三行,其中只有一行包含 "duct"。我们断言`search`函数的返回值只包含期望的那一行。
+
+我们还不能运行这个测试并看到它失败,因为它甚至都还不能编译!我们将增加足够的代码来使其能够编译:一个总是会返回空 vector 的`search`函数定义,如列表 12-16 所示。一旦有了它,这个测试应该能够编译并因为空 vector 并不匹配一个包含一行`"safe, fast, productive."`的 vector 而失败。
+
+Filename: src/lib.rs
+
+```rust
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+ vec![]
+}
+```
+
+Listing 12-16: Defining just enough of the `search`
+function that our test will compile
-注意需要在`grep`的签名中显式声明声明周期`'a`并用于`contents`参数和返回值。记住,生命周期参数用于指定函数参数于返回值的生命周期的关系。在这个例子中,我们表明返回的 vector 将包含引用参数`contents`的字符串 slice,而不是引用参数`search`的字符串 slice。换一种说法就是我们告诉 Rust 函数`grep`返回的数据将和传递给它的参数`contents`的数据存活的同样久。这是非常重要的!考虑为了使引用有效则 slice 引用的数据也需要保持有效,如果编译器认为我们是在创建`search`而不是`contents`的 slice,那么安全检查将是不正确的。如果尝试不用生命周期编译的话,我们将得到如下错误:
+注意需要在`search`的签名中显式定义一个显式生命周期`'a`并用于`contents`参数和返回值。回忆一下第十章中生命周期参数指定哪个参数的生命周期与返回值的生命周期相关联。在这个例子中,我们表明返回的 vector 中应该包含引用参数`contents`(而不是参数`query`) slice 的字符串 slice。
+
+换句话说,我们告诉 Rust 函数`search`返回的数据将与`search`函数中的参数`contents`的数据存在的一样久。这是非常重要的!为了使这个引用有效那么**被**slice 引用的数据也需要保持有效;如果编译器认为我们是在创建`query`而不是`contents`的字符串 slice,那么安全检查将是不正确的。
+
+如果尝试不用生命周期编译的话,我们将得到如下错误:
```
error[E0106]: missing lifetime specifier
- --> src\lib.rs:37:46
- |
-37 | fn grep(search: &str, contents: &str) -> Vec<&str> {
- | ^ expected lifetime parameter
- |
- = help: this function's return type contains a borrowed value, but the
- signature does not say whether it is borrowed from `search` or
- `contents`
+ --> src/lib.rs:5:47
+ |
+5 | fn search(query: &str, contents: &str) -> Vec<&str> {
+ | ^ expected lifetime parameter
+ |
+ = help: this function's return type contains a borrowed value, but the
+ signature does not say whether it is borrowed from `query` or `contents`
```
Rust 不可能知道我们需要的是哪一个参数,所以需要告诉它。因为参数`contents`包含了所有的文本而且我们希望返回匹配的那部分文本,而我们知道`contents`是应该要使用生命周期语法来与返回值相关联的参数。
-在函数签名中将参数与返回值相关联是其他语言不会让你做的工作,所以不用担心这感觉很奇怪!掌握如何指定生命周期会随着时间的推移越来越容易,熟能生巧。你可能想要重新阅读上一部分或返回与第十章中生命周期语法部分的例子做对比。
+其他语言中并不需要你在函数签名中将参数与返回值相关联,所以这么做可能仍然感觉有些陌生,随着时间的推移会越来越容易。你可能想要将这个例子与第十章中生命周期语法部分做对比。
-现在试试运行测试:
+现在试尝试运行测试:
```
$ cargo test
@@ -107,54 +116,81 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
error: test failed
```
-好的,测试失败了,这正是我们所期望的。修改代码来让测试通过吧!之所以会失败是因为我们总是返回一个空的 vector。如下是如何实现`grep`的步骤:
+好的,测试失败了,这正是我们所期望的。修改代码来让测试通过吧!
+
+### 编写使测试通过的代码
+
+目前测试之所以会失败是因为我们总是返回一个空的 vector。为了修复并实现`search`,我们的程序需要遵循如下步骤:
1. 遍历每一行文本。
2. 查看这一行是否包含要搜索的字符串。
- * 如果有,将这一行加入返回列表中
- * 如果没有,什么也不做
+ * 如果有,将这一行加入返回列表中。
+ * 如果没有,什么也不做。
3. 返回匹配到的列表
-让我们一步一步的来,从遍历每行开始。字符串类型有一个有用的方法来处理这种情况,它刚好叫做`lines`:
+让我们一步一步的来,从遍历每行开始。
+
+#### 使用`lines`方法遍历每一行
+
+Rust 有一个有助于一行一行遍历字符串的方法,出于方便它被成为`lines`,它如列表 12-17 这样工作:
Filename: src/lib.rs
```rust,ignore
-fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
// do something with line
}
}
```
+Listing 12-17: Iterating through each line in
+`contents`
+
-我们使用了一个`for`循环和`lines`方法来依次获得每一行。接下来,让我们看看这些行是否包含要搜索的字符串。幸运的是,字符串类型为此也有一个有用的方法`contains`!`contains`的用法看起来像这样:
+`lines`方法返回一个迭代器。第十三张会深入了解迭代器,不过我们已经在列表 3-6 中见过使用迭代器的方法,在那里使用了一个`for`循环和迭代器在一个集合的每一项上运行一些代码。
+
+
+
+
+#### 用查询字符串搜索每一行
+
+接下来将会增加检查当前行是否包含查询字符串的功能。幸运的是,字符串类型为此也有一个有用的方法叫做`contains`!如列表 12-18 所示在`search`函数中加入`contains`方法:
Filename: src/lib.rs
```rust,ignore
-fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
// do something with line
}
}
}
```
+Listing 12-18: Adding functionality to see if the line
+contains the string in `query`
+
-最终,我们需要一个方法来存储包含要搜索字符串的行。为此可以在`for`循环之前创建一个可变的 vector 并调用`push`方法来存放一个`line`。在`for`循环之后,返回这个 vector。列表 12-15 中为完整的实现:
+#### 存储匹配的行
+
+最后我们需要一个方法来存储包含查询字符串的行。为此可以在`for`循环之前创建一个可变的 vector 并调用`push`方法在 vector 中存放一个`line`。在`for`循环之后,返回这个 vector,如列表 12-19 所示:
Filename: src/lib.rs
-```rust
-fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+```rust,ignore
+fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
- if line.contains(search) {
+ if line.contains(query) {
results.push(line);
}
}
@@ -163,12 +199,12 @@ fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
-Listing 12-15: Fully functioning implementation of the
-`grep` function
+Listing 12-19: Storing the lines that match so that we
+can return them
-尝试运行一下:
+现在`search`函数应该返回只包含`query`的那些行,而测试应该会通过。让我们运行测试:
```
$ cargo test
@@ -190,9 +226,24 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```
-非常好!它可以工作了。现在测试通过了,我们可以考虑一下重构`grep`的实现并时刻保持其功能不变。这些代码并不坏,不过并没有利用迭代器的一些实用功能。第十三章将回到这个例子并探索迭代器和如何改进代码。
+测试通过了,很好,它可以工作了!
-现在`grep`函数是可以工作的,我们还需在在`run`函数中做最后一件事:还没有打印出结果呢!增加一个`for`循环来打印出`grep`函数返回的每一行:
+现在测试通过了,我们可以考虑一下重构`search`的实现并时刻保持测试通过来保持其功能不变的机会了。这些代码并不坏,不过并没有利用迭代器的一些实用功能。第十三章将回到这个例子并深入探索迭代器并看看如何改进代码。
+
+
+
+
+#### 在`run`函数中使用`search`函数
+
+现在`search`函数是可以工作并测试通过了的,我们需要实际在`run`函数中调用`search`。需要将`config.query`值和`run`从文件中读取的`contents`传递给`search`函数。接着`run`会打印出`search`返回的每一行:
Filename: src/lib.rs
@@ -203,7 +254,7 @@ pub fn run(config: Config) -> Result<(), Box> {
let mut contents = String::new();
f.read_to_string(&mut contents)?;
- for line in grep(&config.search, &contents) {
+ for line in search(&config.query, &contents) {
println!("{}", line);
}
@@ -213,26 +264,36 @@ pub fn run(config: Config) -> Result<(), Box> {
-现在程序应该能正常运行了!试试吧:
+这里再一次使用了`for`循环获取了`search`返回的每一行,而对每一行运行的代码将他们打印了出来。
+
+现在整个程序应该可以工作了!让我们试一试,首先使用一个只会在艾米莉·狄金森的诗中返回一行的单词 "frog":
+
+```
+$ cargo run frog poem.txt
+ Compiling greprs v0.1.0 (file:///projects/greprs)
+ Finished debug [unoptimized + debuginfo] target(s) in 0.38 secs
+ Running `target/debug/greprs frog poem.txt`
+How public, like a frog
+```
+
+好的!接下来,像 "the" 这样会匹配多行的单词会怎么样呢:
```
$ cargo run the poem.txt
- Compiling greprs v0.1.0 (file:///projects/greprs)
- Finished debug [unoptimized + debuginfo] target(s) in 0.38 secs
- Running `target\debug\greprs.exe the poem.txt`
-Then there's a pair of us - don't tell!
-To tell your name the livelong day
-
-$ cargo run a poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe a poem.txt`
-I'm nobody! Who are you?
-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
+ Running `target/debug/greprs the poem.txt`
+Then there's a pair of us — don't tell!
To tell your name the livelong day
-To an admiring bog!
```
-好极了!我们创建了一个属于自己的经典工具,并学习了很多如何组织程序的知识。我们还学习了一些文件输入输出、生命周期、测试和命令行解析的内容。
\ No newline at end of file
+最后,让我们确保搜索一个在诗中哪里都没有的单词时不会得到任何行,比如 "monomorphization":
+
+```
+$ cargo run monomorphization poem.txt
+ Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+ Running `target/debug/greprs monomorphization poem.txt`
+```
+
+非常好!我们创建了一个属于自己的经典工具,并学习了很多如何组织程序的知识。我们还学习了一些文件输入输出、生命周期、测试和命令行解析的内容。
+
+现在如果你希望的话请随意移动到第十三章。为了使这个项目章节更丰满,我们将简要的展示如何处理环境变量和打印到标准错误,这两者在编写命令行程序时都很有用。
\ No newline at end of file
diff --git a/src/ch12-05-working-with-environment-variables.md b/src/ch12-05-working-with-environment-variables.md
index 5e3a84d..de13051 100644
--- a/src/ch12-05-working-with-environment-variables.md
+++ b/src/ch12-05-working-with-environment-variables.md
@@ -2,22 +2,34 @@
> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-05-working-with-environment-variables.md)
>
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+> commit 0db6a0a34886bf02feabcab8b430b5d332a8bdf5
-让我们再增加一个功能:大小写不敏感搜索。另外,这个设定将不是一个命令行参数:相反它将是一个环境变量。当然可以选择创建一个大小写不敏感的命令行参数,不过用户要求提供一个环境变量这样设置一次之后在整个终端会话中所有的搜索都将是大小写不敏感的了。
+我们将用一个额外的功能来改进我们的工具:一个通过环境变量启用的大小写不敏感搜索的选项。我们将其设计为一个命令行参数并要求用户每次需要时都加上它,不过相反我们将使用环境变量。这允许用户设置环境变脸一次之后在整个终端会话中所有的搜索都将是大小写不敏感的了。
-### 实现并测试一个大小写不敏感`grep`函数
+### 编写一个大小写不敏感`search`函数的失败测试
-首先,让我们增加一个新函数,当设置了环境变量时会调用它。增加一个新测试并重命名已经存在的那个:
+首先,增加一个新函数,当设置了环境变量时会调用它。
-```rust,ignore
+
+
+
+这里将继续遵循上一部分开始使用的 TDD 过程,其第一步是再次编写一个失败测试。我们将为新的大小写不敏感搜索函数新增一个测试函数,并将老的测试函数从`one_result`改名为`case_sensitive`来更清除的表明这两个测试的区别,如列表 12-20 所示:
+
+Filename: src/lib.rs
+
+```rust
#[cfg(test)]
mod test {
- use {grep, grep_case_insensitive};
+ use super::*;
#[test]
fn case_sensitive() {
- let search = "duct";
+ let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
@@ -26,13 +38,13 @@ Duct tape.";
assert_eq!(
vec!["safe, fast, productive."],
- grep(search, contents)
+ search(query, contents)
);
}
#[test]
fn case_insensitive() {
- let search = "rust";
+ let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
@@ -41,7 +53,7 @@ Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
- grep_case_insensitive(search, contents)
+ search_case_insensitive(query, contents)
);
}
}
@@ -49,17 +61,26 @@ Trust me.";
-我们将定义一个叫做`grep_case_insensitive`的新函数。它的实现与`grep`函数大体上相似,不过列表 12-16 展示了一些小的区别:
+Listing 12-20: Adding a new failing test for the case
+insensitive function we're about to add
+
+注意我们也改变了老测试中`query`和`contents`的值:将查询字符串改变为 "duct",它将会匹配带有单词 productive" 的行。还新增了一个含有文本 "Duct tape" 的行,它有一个大写的 D,这在大小写敏感搜索时不应该匹配 "duct"。我们修改这个测试以确保不会意外破坏已经实现的大小写敏感搜索功能;这个测试现在应该能通过并在处理大小写不敏感搜索时应该能一直通过。
+
+大小写不敏感搜索的新测试使用带有一些大写字母的 "rUsT" 作为其查询字符串。我们将要增加的`search_case_insensitive`的期望返回值是包含查询字符串 "rust" 的两行,"Rust:" 包含一个大写的 R 还有"Trust me."包含一个小写的 r。这个测试现在会编译失败因为还没有定义`search_case_insensitive`函数;请随意增加一个总是返回空 vector 的骨架实现,正如列表 12-16 中`search`函数那样为了使测试编译并失败时所做的那样。
+
+### 实现`search_case_insensitive`函数
+
+`search_case_insensitive`函数,如列表 12-21 所示,将与`search`函数基本相同。区别是它会将`query`变量和每一`line`都变为小写,这样不管输入参数是大写还是小写,在检查该行是否包含查询字符串时都会是小写。
Filename: src/lib.rs
```rust
-fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
- let search = search.to_lowercase();
+fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
+ let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
- if line.to_lowercase().contains(&search) {
+ if line.to_lowercase().contains(&query) {
results.push(line);
}
}
@@ -68,19 +89,32 @@ fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
-Listing 12-16: Implementing a `grep_case_insensitive`
-function by changing the search string and the lines of the contents to
-lowercase before comparing them
+Listing 12-21: Defining the `search_case_insensitive`
+function to lowercase both the query and the line before comparing them
-首先,将`search`字符串转换为小写,并存放于一个同名的覆盖变量中。注意现在`search`是一个`String`而不是字符串 slice,所以在将`search`传递给`contains`时需要加上 &,因为`contains`获取一个字符串 slice。
+
+
-接着在检查每个`line`是否包含`search`之前增加了一个`to_lowercase`调用。因为将`line`和`search`都转换为小写,我们就可以无视大小写的匹配文件和命令行参数了。看看测试是否通过了:
+首先我们将`query`字符串转换为小写,并将其储存(覆盖)到同名的变量中。对查询字符串调用`to_lowercase`是必需的这样不管用户的查询是"rust"、"RUST"、"Rust"或者"rUsT",我们都将其当作"rust"处理并对大小写不敏感。
+
+注意`query`现在是一个`String`而不是字符串 slice,因为调用`to_lowercase`是在创建新数据,而不是引用现有数据。如果查询字符串是"rUsT",这个字符串 slice 并不包含可供我们使用的小写的 u,所以必需分配一个包含"rust"的新`String`。因为`query`现在是一个`String`,当我们将`query`作为一个参数传递给`contains`方法时,需要增加一个 & 因为`contains`的签名被定义为获取一个字符串 slice。
+
+接下来在检查每个`line`是否包含`search`之前增加了一个`to_lowercase`调用。这会将"Rust:"变为"rust:"并将"Trust me."变为"trust me."。现在我们将`line`和`query`都转换成了小写,这样就可以不管大小写的匹配文件中的文本和用户输入的查询了。
+
+让我们看看这个实现能否通过测试:
```
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running target\debug\deps\greprs-e58e9b12d35dc861.exe
+ Running target/debug/deps/greprs-e58e9b12d35dc861
running 2 tests
test test::case_insensitive ... ok
@@ -88,7 +122,7 @@ test test::case_sensitive ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
- Running target\debug\greprs-8a7faa2662b5030a.exe
+ Running target/debug/greprs-8a7faa2662b5030a
running 0 tests
@@ -101,13 +135,13 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```
-好的!现在,我们必须真正的使用新的`grep_case_insensitive`函数。首先,在`Config`结构体中增加一个配置项:
+好的!现在,让我们在`run`函数中调用真正的新`search_case_insensitive`函数。首先,我们将在`Config`结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。
Filename: src/lib.rs
```rust
pub struct Config {
- pub search: String,
+ pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
@@ -115,11 +149,29 @@ pub struct Config {
-接着在`run`函数中检查这个选项,并根据`case_sensitive`函数的值来决定调用哪个函数:
+这里增加了`case_sensitive`字符来存放一个布尔值。接着我们需要`run`函数检查`case_sensitive`字段的值并使用它来决定是否调用`search`函数或`search_case_insensitive`函数,如列表 12-22所示:
Filename: src/lib.rs
-```rust,ignore
+```rust
+# use std::error::Error;
+# use std::fs::File;
+# use std::io::prelude::*;
+#
+# 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> {
+# vec![]
+# }
+#
+# struct Config {
+# query: String,
+# filename: String,
+# case_sensitive: bool,
+# }
+#
pub fn run(config: Config) -> Result<(), Box>{
let mut f = File::open(config.filename)?;
@@ -127,9 +179,9 @@ pub fn run(config: Config) -> Result<(), Box>{
f.read_to_string(&mut contents)?;
let results = if config.case_sensitive {
- grep(&config.search, &contents)
+ search(&config.query, &contents)
} else {
- grep_case_insensitive(&config.search, &contents)
+ search_case_insensitive(&config.query, &contents)
};
for line in results {
@@ -140,48 +192,38 @@ pub fn run(config: Config) -> Result<(), Box>{
}
```
+Listing 12-22: Calling either `search` or
+`search_case_insensitive` based on the value in `config.case_sensitive`
+
-最后需要真正的检查环境变量。为了将标准库中的`env`模块引入作用域,在 *src/lib.rs* 开头增加一个`use`行:
+最后需要实际检查环境变量。处理环境变量的函数位于标准库的`env`模块中,所以我们需要在 *src/lib.rs* 的开头增加一个`use std::env;`行将这个模块引入作用域中。接着在`Config::new`中使用`env`模块的`var`方法检查一个叫做`CASE_INSENSITIVE`的环境变量,如列表 12-23 所示:
Filename: src/lib.rs
```rust
use std::env;
-```
-
-并接着在`Config::new`中使用`env`模块的`vars`方法:
-
-Filename: src/lib.rs
-
-```rust
-# use std::env;
-#
# struct Config {
-# search: String,
+# query: String,
# filename: String,
# case_sensitive: bool,
# }
-#
+
+// ...snip...
+
impl Config {
pub fn new(args: &[String]) -> Result {
if args.len() < 3 {
return Err("not enough arguments");
}
- let search = args[1].clone();
+ let query = args[1].clone();
let filename = args[2].clone();
- let mut case_sensitive = true;
-
- for (name, _) in env::vars() {
- if name == "CASE_INSENSITIVE" {
- case_sensitive = false;
- }
- }
+ let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
- search: search,
+ query: query,
filename: filename,
case_sensitive: case_sensitive,
})
@@ -189,32 +231,37 @@ impl Config {
}
```
+Listing 12-23: Checking for an environment variable named
+`CASE_INSENSITIVE`
+
-这里我们调用了`env::vars`,它与`env::args`的工作方式类似。区别是`env::vars`返回一个环境变量而不是命令行参数的迭代器。不同于使用`collect`来创建一个所有环境变量的 vector,我们使用`for`循环。`env::vars`返回一系列元组:环境变量的名称和其值。我们从来也不关心它的值,只关心它是否被设置了,所以可以使用`_`占位符来取代变量名来让 Rust 知道它不应该警告一个未使用的变量。最后,有一个默认为真的变量`case_sensitive`。如果我们找到了一个`CASE_INSENSITIVE`环境变量,就将`case_sensitive`设置为假。接着将其作为`Config`的一部分返回。
+这里创建了一个新变量`case_sensitive`。为了设置它的值,需要调用`env::var`函数并传递我们需要寻找的环境变量名称,`CASE_INSENSITIVE`。`env::var`返回一个`Result`,它在环境变量被设置时返回包含其值的`Ok`成员,并在环境变量未被设置时返回`Err`成员。我们使用`Result`的`is_err`方法来检查其是否是一个 error(也就是环境变量未被设置的情况),这也就意味着我们**需要**进行一个大小写敏感搜索。如果`CASE_INSENSITIVE`环境变量被设置为任何值,`is_err`会返回 false 并将进行大小写不敏感搜索。我们并不关心环境变量所设置的值,只关心它是否被设置了,所以检查`is_err`而不是`unwrap`、`expect`或任何我们已经见过的`Result`的方法。我们将变量`case_sensitive`的值传递给`Config`实例这样`run`函数可以读取其值并决定是否调用`search`或者列表 12-22 中实现的`search_case_insensitive`。
-尝试运行几次吧!
+让我们试一试吧!首先不设置环境变量并使用查询"to"运行程序,这应该会匹配任何全小写的单词"to"的行:
-```
+```text
$ cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
```
+看起来程序仍然能够工作!现在将`CASE_INSENSITIVE`设置为 1 并仍使用相同的查询"to",这回应该得到包含可能有大写字母的"to"的行:
+
```
$ CASE_INSENSITIVE=1 cargo run to poem.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
- Running `target\debug\greprs.exe to poem.txt`
+ Running `target/debug/greprs to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
```
-好极了!`greprs`现在可以通过环境变量的控制来进行大小写不敏感搜索了。现在你已经知道如何处理命令行参数或环境变量了!
+好极了,我们也得到了包含"To"的行!现在`greprs`程序可以通过环境变量控制进行大小写不敏感搜索了。现在你知道了如何管理由命令行参数或环境变量设置的选项了!
-一些程序允许对相同配置同时使用参数_和_环境变量。在这种情况下,程序来决定参数和环境变量的优先级。作为一个留给你的测试,尝试同时通过一个命令行参数来控制大小写不敏感搜索,并在程序遇到矛盾值时决定其优先级。
+一些程序允许对相同配置同时使用参数**和**环境变量。在这种情况下,程序来决定参数和环境变量的优先级。作为一个留给你的测试,尝试同时通过一个命令行参数来控制大小写不敏感搜索,并在程序遇到矛盾值时决定其优先级。
`std::env`模块还包含了更多处理环境变量的实用功能;请查看官方文档来了解其可用的功能。
\ No newline at end of file
diff --git a/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/src/ch12-06-writing-to-stderr-instead-of-stdout.md
index c8651cb..c7e98f8 100644
--- a/src/ch12-06-writing-to-stderr-instead-of-stdout.md
+++ b/src/ch12-06-writing-to-stderr-instead-of-stdout.md
@@ -2,23 +2,39 @@
> [ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
>
-> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
+> commit d09cfb51a239c0ebfc056a64df48fe5f1f96b207
-目前为止,我们将所有的输出都`println!`到了终端。这是可以的,不过大部分终端都提供了两种输出:“标准输出”对应大部分信息,而“标准错误”则用于错误信息。这使得处理类似于“将错误打印到终端而将其他信息输出到文件”的情况变得更容易。
+目前为止,我们将所有的输出都`println!`到了终端。大部分终端都提供了两种输出:“标准输出”对应大部分信息,而“标准错误”则用于错误信息。这种区别是命令行程序所期望拥有的行为:例如它允许用户选择将程序正常输出定向到一个文件中并仍将错误信息打印到屏幕上。但是`println!`只能够打印到标准输出,所以我们必需使用其他方法来打印到标准错误。
-可以通过在命令行使用`>`来将输出重定向到文件中,同时不使用任何参数运行来造成一个错误,就会发现我们的程序只能打印到`stdout`:
+我们可以验证,目前所编写的`greprs`,所有内容都被打印到了标准输出,包括应该被写入标准错误的错误信息。可以通过故意造成错误来做到这一点,一个发生这种情况的方法是不使用任何参数运行程序。我们准备将标准输出重定向到一个文件中,不过不是标准错误。命令行程序期望以这种方式工作,因为如果输出是错误信息,它应该显示在屏幕上而不是被重定向到文件中。可以看出我们的程序目前并没有满足这个期望,通过使用`>`并指定一个文件名,*output.txt*,这是期望将标注输出重定向的文件:
```
$ cargo run > output.txt
```
-`>`语法告诉 shell 将标准输出的内容写入到 *output.txt* 文件中而不是打印到屏幕上。然而,如果运行命令后打开 *output.txt* 就会发现错误:
+
+
+
+`>`语法告诉 shell 将标准输出的内容写入到 *output.txt* 文件中而不是打印到屏幕上。我们并没有看到期望的错误信息打印到屏幕上,所以这意味着它一定被写入了文件中。让我们看看 *output.txt* 包含什么:
```
-Problem parsing arguments: not enough arguments
+Application error: No search string or filename found
```
-我们希望这个信息被打印到屏幕上,而只有成功运行产生的输出写入到文件中。让我们如列表 12-17 中所示改变如何打印错误信息的方法:
+
+
+
+是的,这就是错误信息,这意味着它被打印到了标准输出。这并不是命令行程序所期望拥有的。像这样的错误信息被打印到标准错误,并当以这种方式重定向标注输出时只将运行成功时的数据打印到文件中。让我们像列表 12-23 所示改变错误信息如何被打印的。因为本章早些时候的进行的重构,所有打印错误信息的代码都在一个位置,在`main`中:
Filename: src/main.rs
@@ -32,8 +48,8 @@ use std::io::prelude::*;
use greprs::Config;
fn main() {
- let mut stderr = std::io::stderr();
let args: Vec = env::args().collect();
+ let mut stderr = std::io::stderr();
let config = Config::new(&args).unwrap_or_else(|err| {
writeln!(
@@ -41,12 +57,10 @@ fn main() {
"Problem parsing arguments: {}",
err
).expect("Could not write to stderr");
-
process::exit(1);
});
if let Err(e) = greprs::run(config) {
-
writeln!(
&mut stderr,
"Application error: {}",
@@ -58,27 +72,29 @@ fn main() {
}
```
-Listing 12-17: Writing error messages to `stderr` instead
-of `stdout`
+Listing 12-23: Writing error messages to `stderr` instead
+of `stdout` using `writeln!`
Rust 并没有类似`println!`这样的方便写入标准错误的函数。相反,我们使用`writeln!`宏,它有点像`println!`,不过它获取一个额外的参数。第一个参数是希望写入内容的位置。可以通过`std::io::stderr`函数获取一个标准错误的句柄。我们将一个`stderr`的可变引用传递给`writeln!`;它需要是可变的因为这样才能写入信息!第二个和第三个参数就像`println!`的第一个和第二参数:一个格式化字符串和任何需要插入的变量。
-让我们再次用相同方式运行程序,不带任何参数并用 `>`重定向`stdout`:
+再次用相同方式运行程序,不带任何参数并用`>`重定向`stdout`:
```
$ cargo run > output.txt
-Problem parsing arguments: not enough arguments
+Application error: No search string or filename found
```
-现在我们看到了屏幕上的错误信息,不过 `output.txt` 里什么也没有。如果我们使用正确的参数再次运行:
+现在我们看到了屏幕上的错误信息,不过`output.txt`里什么也没有,这也就是命令行程序所期望的行为。
+
+如果使用不会造成错误的参数再次运行程序,不过仍然将标准输出重定向到一个文件:
```
$ cargo run to poem.txt > output.txt
```
-终端将没有输出,不过 `output.txt` 将会包含其结果:
+我们并不会在终端看到任何输出,同时`output.txt`将会包含其结果:
Filename: output.txt
@@ -87,8 +103,10 @@ Are you nobody, too?
How dreary to be somebody!
```
+这一部分展示了现在我们使用的成功时产生的标准输出和错误时产生的标准错误是恰当的。
+
## 总结
-在这一章,我们涉及了如果在 Rust 中进行常规的 I/O 操作。通过使用命令行参数、文件、环境变量和写入`stderr`的功能。现在你已经准备好编写命令行程序了。结合前几章的知识,你的代码将会是组织良好的,并能有效的将数据存储到合适的数据结构中、更好的处理错误,并且还是经过良好测试的。我们也接触了一个真实情况下需要生命周期注解来保证引用一直有效的场景。
+在这一章中,我们回顾了目前为止的一些主要章节并涉及了如何在 Rust 中进行常规的 I/O 操作。通过使用命令行参数、文件、环境变量和`writeln!`宏与`writeln!`,现在你已经准备好编写命令行程序了。结合前几章的知识,你的代码将会是组织良好的,并能有效的将数据存储到合适的数据结构中、更好的处理错误,并且还是经过良好测试的。
接下来,让我们探索如何利用一些 Rust 中受函数式编程语言影响的功能:闭包和迭代器。
\ No newline at end of file