From a08cee53742b106e9f10b4b68108cebee5b1dd40 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Mon, 4 Sep 2017 23:16:09 +0800 Subject: [PATCH] check ch13-03 --- src/ch02-00-guessing-game-tutorial.md | 2 +- src/ch13-02-iterators.md | 332 +++++++++++++++++++++++- src/ch13-03-improving-our-io-project.md | 117 +++++++-- 3 files changed, 423 insertions(+), 28 deletions(-) diff --git a/src/ch02-00-guessing-game-tutorial.md b/src/ch02-00-guessing-game-tutorial.md index c2c3d08..62130ca 100644 --- a/src/ch02-00-guessing-game-tutorial.md +++ b/src/ch02-00-guessing-game-tutorial.md @@ -4,7 +4,7 @@ >
> commit 2e269ff82193fd65df8a87c06561d74b51ac02f7 -让我们一起动手完成一个项目,来快速上手 RUst!本章将介绍 Rust 中常用的一些概念,并通过真实的程序来展示如何运用他们。你将会学到更多诸如 `let`、`match`、方法、关联函数、外部 crate 等很多的知识!后继章节会深入探索这些概念的细节。在这一章,我们将练习基础。 +让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中常用的一些概念,并通过真实的程序来展示如何运用他们。你将会学到更多诸如 `let`、`match`、方法、关联函数、外部 crate 等很多的知识!后继章节会深入探索这些概念的细节。在这一章,我们将练习基础。 我们会实现一个经典的新手编程问题:猜猜看游戏。它是这么工作的:程序将会随机生成一个 1 到 100 之间的随机整数。接着它会请玩家猜一个数并输入,然后提示猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。 diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 4c2a036..e54dbbc 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -2,8 +2,336 @@ > [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md) >
-> commit b9459971e4bc6f37b4d18b38c6fe9221317fd985 +> commit 40910f557c328858f230123d1234c1cb3029dda3 迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。 -在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,列表 13-13 中的代码创建 \ No newline at end of file +在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,列表 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处: + +```rust +let v1 = vec![1, 2, 3]; + +let v1_iter = v1.iter(); +``` + +列表 13-13:创建了迭代器 + +创建迭代器之后,可以选择用多种方式利用它。在列表 3-6 中,我们实际上使用了迭代器和 `for` 循环在每一个项上执行了一些代码,直到现在我们才解释了 `iter` 调用做了什么。列表 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值: + +```rust +let v1 = vec![1, 2, 3]; + +let v1_iter = v1.iter(); + +for val in v1_iter { + println!("Got: {}", val); +} +``` + +列表 13-14:在一个 `for` 循环中使用迭代器 + +在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。迭代器为我们处理了所有这些逻辑,这减少了重复代码并潜在的消除了混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。 + +### `Iterator` trait 和 `next` 方法 + +迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像: + +```rust +trait Iterator { + type Item; + + fn next(&mut self) -> Option; + + // methods with default implementations elided +} +``` + +这里有一下我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型**(*associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。 + +`next` 是 `Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`。如果你希望的话可以直接调用迭代器的 `next` 方法;列表 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值: + +文件名: src/lib.rs + +```rust,test_harness +#[test] +fn iterator_demonstration() { + let v1 = vec![1, 2, 3]; + + let mut v1_iter = v1.iter(); + + assert_eq!(v1_iter.next(), Some(&1)); + assert_eq!(v1_iter.next(), Some(&2)); + assert_eq!(v1_iter.next(), Some(&3)); + assert_eq!(v1_iter.next(), None); +} +``` + +列表 13-15:在迭代器上(直接)调用 `next` 方法 + +注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**(consume)了,或使用了迭代器。每一个 `next` 调用都会从迭代器中吃掉一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。 + +另外需要注意到从 `next` 调用中得到的值是 vector 的不可变引用。`iter` 方法生成一个不可变引用的迭代器。如果我们需要一个获取 `v1` 所有权并返回拥有所有权的迭代器,则可以调用 `into_iter` 而不是 `iter`。类似的,如果我们希望迭代可变引用,则可以调用 `iter_mut` 而不是 `iter`。 + +### `Iterator` trait 中消费迭代器的方法 + +`Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。 + +这些调用 `next` 方法的方法被称为 **消费适配器**(*consuming adaptors*),因为调用他们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和。列表 13-16 有一个展示 `sum` 方法使用的测试: + +文件名: src/lib.rs + +```rust +#[test] +fn iterator_sum() { + let v1 = vec![1, 2, 3]; + + let v1_iter = v1.iter(); + + let total: i32 = v1_iter.sum(); + + assert_eq!(total, 6); +} +``` + +列表 13-16:调用 `sum` 方法获取迭代器所有项的总和 + +调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。 + +### `Iterator` trait 中产生其他迭代器的方法 + +`Iterator` trait 中定义的另一类方法会产生其他的迭代器。这些方法被称为 **迭代器适配器**(*iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。列表 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,它会获取一个 `map` 会在每一个项上调用的闭包来产生一个新迭代器,它的每一项为 vector 中每一项加一。不过这些代码会产生一个警告: + +文件名: src/main.rs + +```rust +let v1: Vec = vec![1, 2, 3]; + +v1.iter().map(|x| x + 1); +``` + +列表 13-17:调用迭代器适配器 `map` 来创建一个新迭代器 + +得到的警告是: + +```text +warning: unused result which must be used: iterator adaptors are lazy and do +nothing unless consumed + --> src/main.rs:4:1 + | +4 | v1.iter().map(|x| x + 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: #[warn(unused_must_use)] on by default +``` + +列表 13-17 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们可能意在消费迭代器。 + +为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章简要讲到的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。在列表 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加一的结果: + +文件名: src/main.rs + +```rust +let v1: Vec = vec![1, 2, 3]; + +let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + +assert_eq!(v2, vec![2, 3, 4]); +``` + +列表 13-18:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector + +因为 `map` 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。这是一个展示如何使用闭包来自定义行为同时又复用 `Iterator` trait 提供的迭代行为的绝佳例子。 + +### 使用闭包获取环境与迭代器 + +现在我们介绍了迭代器,让我们展示一个通过使用 `filter` 迭代器适配器和捕获环境的闭包的常规用例。迭代器的 `filter` 方法获取一个使用迭代器的每一个项并返回布尔值的闭包。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会包含在结果迭代器中。列表 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子: + +文件名: src/lib.rs + +```rust,test_harness +#[derive(PartialEq, Debug)] +struct Shoe { + size: i32, + style: String, +} + +fn shoes_in_my_size(shoes: Vec, shoe_size: i32) -> Vec { + shoes.into_iter() + .filter(|s| s.size == shoe_size) + .collect() +} + +#[test] +fn filters_by_size() { + let shoes = vec![ + Shoe { size: 10, style: String::from("sneaker") }, + Shoe { size: 13, style: String::from("sandal") }, + Shoe { size: 10, style: String::from("boot") }, + ]; + + let in_my_size = shoes_in_my_size(shoes, 10); + + assert_eq!( + in_my_size, + vec![ + Shoe { size: 10, style: String::from("sneaker") }, + Shoe { size: 10, style: String::from("boot") }, + ] + ); +} +``` + +列表 13-19:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包 + +`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。在 `shoes_in_my_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成只含有闭包返回 `true` 元素的新迭代器。我们指定的闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。 + +这个测试展示当调用 `shoes_in_my_size` 时,我们只会得到与指定值相同大小的鞋子。 + +### 实现 `Iterator` trait 来创建自定义迭代器 + +我们已经展示了可以通过在 vector 上调用 `iter`、`into_iter` 或 `iter_mut` 来创建一个迭代器。也可以用标准库中其他的集合类型创建迭代器,比如哈希 map。另外,可以实现 `Iterator` trait 来创建任何我们希望的迭代器。正如之前提到的,定义中唯一要求提供的方法就是 `next` 方法。一旦定义了它,就可以使用所有其他由 `Iterator` trait 提供的拥有默认实现的方法来创建自定义迭代器了! + +我们将要创建的迭代器只会从 1 数到 5。首先,我们会创建一个结构体来存放一些值,接着实现 `Iterator` trait 将这个结构体放入迭代器中并在此实现中使用其值。 + +列表 13-20 有一个 `Counter` 结构体定义和一个创建 `Counter` 实例的关联函数 `new`: + + +文件名: src/lib.rs + +```rust +struct Counter { + count: u32, +} + +impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } +} +``` + +列表 13-20:定义 `Counter` 结构体和一个创建 `count` 初值为 0 的 `Counter` 实例的 `new` 函数 + +`Counter` 结构体有一个字段 `count`。这个字段存放一个 `u32` 值,它会记录处理 1 到 5 的迭代过程中的位置。`count` 是私有的因为我们希望 `Counter` 的实现来管理这个值。`new` 函数通过总是从为 0 的 `count` 字段开始新实例来确保我们需要的行为。 + +接下来将为 `Counter` 类型实现 `Iterator` trait,通过定义 `next` 方法来指定使用迭代器时的行为,如列表 13-21 所示: + +文件名: src/lib.rs + +```rust +# struct Counter { +# count: u32, +# } +# +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + self.count += 1; + + if self.count < 6 { + Some(self.count) + } else { + None + } + } +} +``` + +列表 13-21:在 `Counter` 结构体上实现 `Iterator` trait + +这里将迭代器的关联类型 `Item` 设置为 `u32`,意味着迭代器会返回 `u32` 值集合。再一次,这里仍无需担心关联类型,第十九章会讲到。我们希望迭代器对其内部状态加一,这也就是为何将 `count` 初始化为 0:我们希望迭代器首先返回 1。如果 `count` 值小于 6,`next` 会返回封装在 `Some` 中的当前值,不过如果 `count` 大于或等于 6,迭代器会返回 `None`。 + +#### 使用 `Counter` 迭代器的 `next` 方法 + +一旦实现了 `Iterator` trait,我们就有了一个迭代器!列表 13-22 展示了一个测试用来演示现在我们可以使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如列表 13-15 中从 vector 创建的迭代器那样: + +文件名: src/lib.rs + +```rust +# struct Counter { +# count: u32, +# } +# +# impl Iterator for Counter { +# type Item = u32; +# +# fn next(&mut self) -> Option { +# self.count += 1; +# +# if self.count < 6 { +# Some(self.count) +# } else { +# None +# } +# } +# } +# +#[test] +fn calling_next_directly() { + let mut counter = Counter::new(); + + assert_eq!(counter.next(), Some(1)); + assert_eq!(counter.next(), Some(2)); + assert_eq!(counter.next(), Some(3)); + assert_eq!(counter.next(), Some(4)); + assert_eq!(counter.next(), Some(5)); + assert_eq!(counter.next(), None); +} +``` + +列表 13-22:测试 `next` 方法实现的功能 + +这个测试在 `counter` 变量中新建了一个 `Counter` 实例并接着反复调用 `next` 方法,来验证我们实现的行为符合这个迭代器返回从 1 到 5 的值的预期。 + +#### 使用自定义迭代器中其他 `Iterator` trait 方法 + +通过定义 `next` 方法实现 `Iterator` trait,我们现在就可以使用任何标准库定义的拥有默认实现的 `Iterator` trait 方法了,因为他们都使用了 `next` 方法的功能。 + +例如,出于某种原因我们希望获取 `Counter` 实例产生的值,将这些值与另一个 `Counter` 实例在省略了第一个值之后产生的值配对,将每一对值相乘,只保留那些可以被三整除的结果,然后将所有保留的结果相加,这可以如列表 13-23 中的测试这样做: + +文件名: src/lib.rs + +```rust +# struct Counter { +# count: u32, +# } +# +# impl Counter { +# fn new() -> Counter { +# Counter { count: 0 } +# } +# } +# +# impl Iterator for Counter { +# // Our iterator will produce u32s +# type Item = u32; +# +# fn next(&mut self) -> Option { +# // increment our count. This is why we started at zero. +# self.count += 1; +# +# // check to see if we've finished counting or not. +# if self.count < 6 { +# Some(self.count) +# } else { +# None +# } +# } +# } +# +#[test] +fn using_other_iterator_trait_methods() { + let sum: u32 = Counter::new().zip(Counter::new().skip(1)) + .map(|(a, b)| a * b) + .filter(|x| x % 3 == 0) + .sum(); + assert_eq!(18, sum); +} +``` + +列表 13-23:使用自定义的 `Counter` 迭代器的多种方法 + +注意 `zip` 只产生4对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`。 + +所有这些方法调用都是可能的,因为我们通过指定 `next` 如何工作来实现 `Iterator` trait 而标准库则提供其他调用 `next` 的默认方法实现。 \ No newline at end of file diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index 09ac7b2..e0294ca 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -2,17 +2,19 @@ > [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-03-improving-our-io-project.md) >
-> commit 0608e2d0743951d8e628b6e130c6b5744775a783 +> commit 714be7f0d6b2f6110afe8808a7f528f9eae75c61 -在我们上一章实现的`grep` I/O 项目中,其中有一些地方的代码可以使用迭代器来变得更清楚简洁一些。让我们看看迭代器如何能够改进`Config::new`函数和`search`函数的实现。 +我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。 -### 使用迭代器并去掉`clone` +### 使用迭代器并去掉 `clone` -回到列表 12-8 中,这些代码获取一个`String` slice 并创建一个`Config`结构体的实例,它检查参数的数量、索引 slice 中的值、并克隆这些值以便`Config`可以拥有他们的所有权: +在列表 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在列表 13-24 中原原本本的重现了第十二章结尾 `Config::new` 函数的实现: + +文件名: src/lib.rs ```rust,ignore impl Config { - fn new(args: &[String]) -> Result { + pub fn new(args: &[String]) -> Result { if args.len() < 3 { return Err("not enough arguments"); } @@ -20,48 +22,90 @@ impl Config { let query = args[1].clone(); let filename = args[2].clone(); - Ok(Config { - query: query, - filename: filename, - }) + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { query, filename, case_sensitive }) } } ``` -当时我们说不必担心这里的`clone`调用,因为将来会移除他们。好吧,就是现在了!所以,为什么这里需要`clone`呢?这里的问题是参数`args`中有一个`String`元素的 slice,而`new`函数并不拥有`args`。为了能够返回`Config`实例的所有权,我们需要克隆`Config`中字段`query`和`filename`的值,这样`Config`就能拥有这些值了。 +列表 13-24:重现第十二章结尾的 `Config::new` 函数 -现在在认识了迭代器之后,我们可以将`new`函数改为获取一个有所有权的迭代器作为参数。可以使用迭代器来代替之前必要的 slice 长度检查和特定位置的索引。因为我们获取了迭代器的所有权,就不再需要借用所有权的索引操作了,我们可以直接将迭代器中的`String`值移动到`Config`中,而不用调用`clone`来创建一个新的实例。 +这时可以不必担心低效的 `clone` 调用了,因为将来可以去掉他们。好吧,就是现在! -首先,让我们看看列表 12-6 中的`main`函数,将`env::args`的返回值改为传递给`Config::new`,而不是调用`collect`并传递一个 slice: +起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice,而 `new` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query` 和 `filename` 的值,这样 `Config` 实例就能拥有这些值。 + +通过迭代器的新知识,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会清理 `Config::new` 的工作因为迭代器会负责访问这些值。 + +一旦 `Config::new` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。 + +#### 直接使用 `env::args` 返回的迭代器 + +在 I/O 项目的 *src/main.rs* 中,让我们修改第十二章结尾 `main` 函数中的这些代码: ```rust,ignore fn main() { - let config = Config::new(env::args()); + let args: Vec = env::args().collect(); + + let config = Config::new(&args).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); + // ...snip... +} ``` - +将他们改为如列表 13-25 所示: -如果参看标准库中`env::args`函数的文档,我们会发现它的返回值类型是`std::env::Args`。所以下一步就是更新`Config::new`的签名使得参数`args`拥有`std::env::Args`类型而不是`&[String]`: +文件名: src/main.rs + +```rust,ignore +fn main() { + let config = Config::new(env::args()).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); + + // ...snip... +} +``` + +列表 13-25:将 `env::args` 的返回值传递给 `Config::new` + +`env::args` 函数返回一个迭代器!不同于将迭代器的值收集到一个 vector 中接着传递一个 slice 给 `Config::new`,现在我们直接将 `env::args` 返回的迭代器的所有权传递给 `Config::new`。 + +接下来需要更新 `Config::new` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::new` 的签名改为如列表 13-26 所示: + +文件名: src/lib.rs ```rust,ignore impl Config { - fn new(args: std::env::Args) -> Result { + pub fn new(args: std::env::Args) -> Result { // ...snip... ``` - +列表 13-26:更新 `Config::new` 的签名来接受一个迭代器 -之后我们将修复`Config::new`的函数体。因为标准库文档也表明,`std::env::Args`实现了`Iterator` trait,所以我们知道可以调用其`next`方法!如下就是新的代码: +`env::args` 函数的标准库文档展示了其返回的迭代器类型是 `std::env::Args`。需要更新 `Config::new` 函数的签名中 `args` 参数的类型为 `std::env::Args` 而不是 `&[String]`。 + +#### 使用 `Iterator` trait 方法带起索引 + +接下来修复 `Config::new` 的函数体。标准库文档也提到了 `std::env::Args` 实现了 `Iterator` trait,所以可以在其上调用 `next` 方法!列表 13-27 更新了列表 12-23 中的代码为使用 `next` 方法: + +文件名: src/lib.rs ```rust +# use std::env; +# # struct Config { # query: String, # filename: String, +# case_sensitive: bool, # } # impl Config { - fn new(mut args: std::env::Args) -> Result { + pub fn new(mut args: std::env::Args) -> Result { args.next(); let query = match args.next() { @@ -74,22 +118,45 @@ impl Config { None => return Err("Didn't get a file name"), }; + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + Ok(Config { - query: query, - filename: filename, + query, filename, case_sensitive }) } } ``` - +列表 13-27:修改 `Config::new` 的函数体为使用迭代器方法 -还记得`env::args`返回值的第一个值是程序的名称吗。我们希望忽略它,所以首先调用`next`并不处理其返回值。第二次调用`next`的返回值应该是希望放入`Config`中`query`字段的值。使用`match`来在`next`返回`Some`时提取值,而在因为没有足够的参数(这会造成`next`调用返回`None`)而提早返回`Err`值。 - -对`filename`值也进行相同处理。稍微有些可惜的是`query`和`filename`的`match`表达式是如此的相似。如果可以对`next`返回的`Option`使用`?`就好了,不过目前`?`只能用于`Result`值。即便我们可以像`Result`一样对`Option`使用`?`,得到的值也是借用的,而我们希望能够将迭代器中的`String`移动到`Config`中。 +请记住 `env::args` 返回值的第一个值是程序的名称。我们希望忽略它并获取下一个值,所以首先调用 `next` 并不对返回值做任何操作。之后对希望放入 `Config` 中字段 `query` 调用 `next`。如果 `next` 返回 `Some`,使用 `match` 来提取其值。如果它返回 `None`,则意味着没有提供足够的参数并通过 `Err` 值提早返回。对 `filename` 值进行同样的操作。 ### 使用迭代器适配器来使代码更简明 +I/O 项目中其他可以利用迭代器优势的地方位于 `search` 函数,在列表 13-28 中重现了第十二章结尾的此函数定义: + +文件名: src/lib.rs + +```rust,ignore +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + let mut results = Vec::new(); + + for line in contents.lines() { + if line.contains(query) { + results.push(line); + } + } + + results +} +``` + +列表 13-28:第十二章结尾 `search` 函数的定义 + +可以通过使用迭代器适配器方法来编写更短的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态 + + + 另一部分可以利用迭代器的代码位于列表 12-15 中实现的`search`函数中: