mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
check ch13-03
This commit is contained in:
parent
06901f5ac7
commit
a08cee5374
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit 2e269ff82193fd65df8a87c06561d74b51ac02f7
|
||||
|
||||
让我们一起动手完成一个项目,来快速上手 RUst!本章将介绍 Rust 中常用的一些概念,并通过真实的程序来展示如何运用他们。你将会学到更多诸如 `let`、`match`、方法、关联函数、外部 crate 等很多的知识!后继章节会深入探索这些概念的细节。在这一章,我们将练习基础。
|
||||
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中常用的一些概念,并通过真实的程序来展示如何运用他们。你将会学到更多诸如 `let`、`match`、方法、关联函数、外部 crate 等很多的知识!后继章节会深入探索这些概念的细节。在这一章,我们将练习基础。
|
||||
|
||||
我们会实现一个经典的新手编程问题:猜猜看游戏。它是这么工作的:程序将会随机生成一个 1 到 100 之间的随机整数。接着它会请玩家猜一个数并输入,然后提示猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。
|
||||
|
||||
|
@ -2,8 +2,336 @@
|
||||
|
||||
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md)
|
||||
> <br>
|
||||
> commit b9459971e4bc6f37b4d18b38c6fe9221317fd985
|
||||
> commit 40910f557c328858f230123d1234c1cb3029dda3
|
||||
|
||||
迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
|
||||
|
||||
在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,列表 13-13 中的代码创建
|
||||
在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着直到调用方法消费迭代器之前它都不会有效果。例如,列表 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
|
||||
|
||||
```rust
|
||||
let v1 = vec![1, 2, 3];
|
||||
|
||||
let v1_iter = v1.iter();
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-13:创建了迭代器</span>
|
||||
|
||||
创建迭代器之后,可以选择用多种方式利用它。在列表 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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-14:在一个 `for` 循环中使用迭代器</span>
|
||||
|
||||
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。迭代器为我们处理了所有这些逻辑,这减少了重复代码并潜在的消除了混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
|
||||
|
||||
### `Iterator` trait 和 `next` 方法
|
||||
|
||||
迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像:
|
||||
|
||||
```rust
|
||||
trait Iterator {
|
||||
type Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
|
||||
// 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` 方法所得到的值:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-15:在迭代器上(直接)调用 `next` 方法</span>
|
||||
|
||||
注意 `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` 方法使用的测试:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-16:调用 `sum` 方法获取迭代器所有项的总和</span>
|
||||
|
||||
调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。
|
||||
|
||||
### `Iterator` trait 中产生其他迭代器的方法
|
||||
|
||||
`Iterator` trait 中定义的另一类方法会产生其他的迭代器。这些方法被称为 **迭代器适配器**(*iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。列表 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,它会获取一个 `map` 会在每一个项上调用的闭包来产生一个新迭代器,它的每一项为 vector 中每一项加一。不过这些代码会产生一个警告:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
let v1: Vec<i32> = vec![1, 2, 3];
|
||||
|
||||
v1.iter().map(|x| x + 1);
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-17:调用迭代器适配器 `map` 来创建一个新迭代器</span>
|
||||
|
||||
得到的警告是:
|
||||
|
||||
```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 中每个元素加一的结果:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
let v1: Vec<i32> = vec![1, 2, 3];
|
||||
|
||||
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
|
||||
|
||||
assert_eq!(v2, vec![2, 3, 4]);
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-18:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector</span>
|
||||
|
||||
因为 `map` 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。这是一个展示如何使用闭包来自定义行为同时又复用 `Iterator` trait 提供的迭代行为的绝佳例子。
|
||||
|
||||
### 使用闭包获取环境与迭代器
|
||||
|
||||
现在我们介绍了迭代器,让我们展示一个通过使用 `filter` 迭代器适配器和捕获环境的闭包的常规用例。迭代器的 `filter` 方法获取一个使用迭代器的每一个项并返回布尔值的闭包。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会包含在结果迭代器中。列表 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,test_harness
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Shoe {
|
||||
size: i32,
|
||||
style: String,
|
||||
}
|
||||
|
||||
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: i32) -> Vec<Shoe> {
|
||||
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") },
|
||||
]
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-19:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span>
|
||||
|
||||
`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`:
|
||||
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
struct Counter {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
fn new() -> Counter {
|
||||
Counter { count: 0 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-20:定义 `Counter` 结构体和一个创建 `count` 初值为 0 的 `Counter` 实例的 `new` 函数</span>
|
||||
|
||||
`Counter` 结构体有一个字段 `count`。这个字段存放一个 `u32` 值,它会记录处理 1 到 5 的迭代过程中的位置。`count` 是私有的因为我们希望 `Counter` 的实现来管理这个值。`new` 函数通过总是从为 0 的 `count` 字段开始新实例来确保我们需要的行为。
|
||||
|
||||
接下来将为 `Counter` 类型实现 `Iterator` trait,通过定义 `next` 方法来指定使用迭代器时的行为,如列表 13-21 所示:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# struct Counter {
|
||||
# count: u32,
|
||||
# }
|
||||
#
|
||||
impl Iterator for Counter {
|
||||
type Item = u32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.count += 1;
|
||||
|
||||
if self.count < 6 {
|
||||
Some(self.count)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-21:在 `Counter` 结构体上实现 `Iterator` trait</span>
|
||||
|
||||
这里将迭代器的关联类型 `Item` 设置为 `u32`,意味着迭代器会返回 `u32` 值集合。再一次,这里仍无需担心关联类型,第十九章会讲到。我们希望迭代器对其内部状态加一,这也就是为何将 `count` 初始化为 0:我们希望迭代器首先返回 1。如果 `count` 值小于 6,`next` 会返回封装在 `Some` 中的当前值,不过如果 `count` 大于或等于 6,迭代器会返回 `None`。
|
||||
|
||||
#### 使用 `Counter` 迭代器的 `next` 方法
|
||||
|
||||
一旦实现了 `Iterator` trait,我们就有了一个迭代器!列表 13-22 展示了一个测试用来演示现在我们可以使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如列表 13-15 中从 vector 创建的迭代器那样:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# struct Counter {
|
||||
# count: u32,
|
||||
# }
|
||||
#
|
||||
# impl Iterator for Counter {
|
||||
# type Item = u32;
|
||||
#
|
||||
# fn next(&mut self) -> Option<Self::Item> {
|
||||
# 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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-22:测试 `next` 方法实现的功能</span>
|
||||
|
||||
这个测试在 `counter` 变量中新建了一个 `Counter` 实例并接着反复调用 `next` 方法,来验证我们实现的行为符合这个迭代器返回从 1 到 5 的值的预期。
|
||||
|
||||
#### 使用自定义迭代器中其他 `Iterator` trait 方法
|
||||
|
||||
通过定义 `next` 方法实现 `Iterator` trait,我们现在就可以使用任何标准库定义的拥有默认实现的 `Iterator` trait 方法了,因为他们都使用了 `next` 方法的功能。
|
||||
|
||||
例如,出于某种原因我们希望获取 `Counter` 实例产生的值,将这些值与另一个 `Counter` 实例在省略了第一个值之后产生的值配对,将每一对值相乘,只保留那些可以被三整除的结果,然后将所有保留的结果相加,这可以如列表 13-23 中的测试这样做:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```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<Self::Item> {
|
||||
# // 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);
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-23:使用自定义的 `Counter` 迭代器的多种方法</span>
|
||||
|
||||
注意 `zip` 只产生4对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`。
|
||||
|
||||
所有这些方法调用都是可能的,因为我们通过指定 `next` 如何工作来实现 `Iterator` trait 而标准库则提供其他调用 `next` 的默认方法实现。
|
@ -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)
|
||||
> <br>
|
||||
> commit 0608e2d0743951d8e628b6e130c6b5744775a783
|
||||
> commit 714be7f0d6b2f6110afe8808a7f528f9eae75c61
|
||||
|
||||
在我们上一章实现的`grep` I/O 项目中,其中有一些地方的代码可以使用迭代器来变得更清楚简洁一些。让我们看看迭代器如何能够改进`Config::new`函数和`search`函数的实现。
|
||||
我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。
|
||||
|
||||
### 使用迭代器并去掉 `clone`
|
||||
|
||||
回到列表 12-8 中,这些代码获取一个`String` slice 并创建一个`Config`结构体的实例,它检查参数的数量、索引 slice 中的值、并克隆这些值以便`Config`可以拥有他们的所有权:
|
||||
在列表 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在列表 13-24 中原原本本的重现了第十二章结尾 `Config::new` 函数的实现:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
impl Config {
|
||||
fn new(args: &[String]) -> Result<Config, &'static str> {
|
||||
pub fn new(args: &[String]) -> Result<Config, &'static str> {
|
||||
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`就能拥有这些值了。
|
||||
<span class="caption">列表 13-24:重现第十二章结尾的 `Config::new` 函数</span>
|
||||
|
||||
现在在认识了迭代器之后,我们可以将`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<String> = env::args().collect();
|
||||
|
||||
let config = Config::new(&args).unwrap_or_else(|err| {
|
||||
eprintln!("Problem parsing arguments: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
// ...snip...
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Will add ghosting in libreoffice /Carol -->
|
||||
将他们改为如列表 13-25 所示:
|
||||
|
||||
如果参看标准库中`env::args`函数的文档,我们会发现它的返回值类型是`std::env::Args`。所以下一步就是更新`Config::new`的签名使得参数`args`拥有`std::env::Args`类型而不是`&[String]`:
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let config = Config::new(env::args()).unwrap_or_else(|err| {
|
||||
eprintln!("Problem parsing arguments: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
// ...snip...
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-25:将 `env::args` 的返回值传递给 `Config::new`</span>
|
||||
|
||||
`env::args` 函数返回一个迭代器!不同于将迭代器的值收集到一个 vector 中接着传递一个 slice 给 `Config::new`,现在我们直接将 `env::args` 返回的迭代器的所有权传递给 `Config::new`。
|
||||
|
||||
接下来需要更新 `Config::new` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::new` 的签名改为如列表 13-26 所示:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
impl Config {
|
||||
fn new(args: std::env::Args) -> Result<Config, &'static str> {
|
||||
pub fn new(args: std::env::Args) -> Result<Config, &'static str> {
|
||||
// ...snip...
|
||||
```
|
||||
|
||||
<!-- Will add ghosting in libreoffice /Carol -->
|
||||
<span class="caption">列表 13-26:更新 `Config::new` 的签名来接受一个迭代器</span>
|
||||
|
||||
之后我们将修复`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` 方法:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# use std::env;
|
||||
#
|
||||
# struct Config {
|
||||
# query: String,
|
||||
# filename: String,
|
||||
# case_sensitive: bool,
|
||||
# }
|
||||
#
|
||||
impl Config {
|
||||
fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
|
||||
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Will add ghosting and wingdings in libreoffice /Carol -->
|
||||
<span class="caption">列表 13-27:修改 `Config::new` 的函数体为使用迭代器方法</span>
|
||||
|
||||
还记得`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 中重现了第十二章结尾的此函数定义:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">列表 13-28:第十二章结尾 `search` 函数的定义</span>
|
||||
|
||||
可以通过使用迭代器适配器方法来编写更短的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态
|
||||
|
||||
|
||||
|
||||
另一部分可以利用迭代器的代码位于列表 12-15 中实现的`search`函数中:
|
||||
|
||||
<!-- We hadn't had a listing number for this code sample when we submitted
|
||||
|
Loading…
Reference in New Issue
Block a user