update to ch13-04

pull/586/head
KaiserY 3 years ago
parent 324244de9b
commit 0635f02553

@ -2,7 +2,7 @@
> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/main/src/ch13-01-closures.md)
> <br>
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
> commit 8acef6cfd40a36be60a3c62458d9f78e2427e190
Rust 的 **闭包***closures*)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获调用者作用域中的值。我们将展示闭包的这些功能如何复用代码和自定义行为。
@ -17,14 +17,7 @@ Rust 的 **闭包***closures*)是可以保存进变量或作为参数传递
<span class="filename">文件名: src/main.rs</span>
```rust
use std::thread;
use std::time::Duration;
fn simulated_expensive_calculation(intensity: u32) -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
intensity
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs:here}}
```
<span class="caption">示例 13-1一个用来代替假定计算的函数它大约会执行两秒钟</span>
@ -41,16 +34,7 @@ fn simulated_expensive_calculation(intensity: u32) -> u32 {
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;
generate_workout(
simulated_user_specified_value,
simulated_random_number
);
}
# fn generate_workout(intensity: u32, random_number: u32) {}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-02/src/main.rs:here}}
```
<span class="caption">示例 13-2`main` 函数包含了用于 `generate_workout` 函数的模拟用户输入和模拟随机数输入</span>
@ -62,36 +46,7 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
# fn simulated_expensive_calculation(num: u32) -> u32 {
# println!("calculating slowly...");
# thread::sleep(Duration::from_secs(2));
# num
# }
#
fn generate_workout(intensity: u32, random_number: u32) {
if intensity < 25 {
println!(
"Today, do {} pushups!",
simulated_expensive_calculation(intensity)
);
println!(
"Next, do {} situps!",
simulated_expensive_calculation(intensity)
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
simulated_expensive_calculation(intensity)
);
}
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-03/src/main.rs:here}}
```
<span class="caption">示例 13-3程序的业务逻辑它根据输入并调用 `simulated_expensive_calculation` 函数来打印出健身计划</span>
@ -113,46 +68,14 @@ fn generate_workout(intensity: u32, random_number: u32) {
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
# fn simulated_expensive_calculation(num: u32) -> u32 {
# println!("calculating slowly...");
# thread::sleep(Duration::from_secs(2));
# num
# }
#
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_result =
simulated_expensive_calculation(intensity);
if intensity < 25 {
println!(
"Today, do {} pushups!",
expensive_result
);
println!(
"Next, do {} situps!",
expensive_result
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_result
);
}
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-04/src/main.rs:here}}
```
<span class="caption">示例 13-4`simulated_expensive_calculation` 调用提取到一个位置,并将结果储存在变量 `expensive_result`</span>
这个修改统一了 `simulated_expensive_calculation` 调用并解决了第一个 `if` 块中不必要的两次调用函数的问题。不幸的是,现在所有的情况下都需要调用函数并等待结果,包括那个完全不需要这一结果的内部 `if` 块。
我们希望能够在程序的一个位置指定某些代码,并只在程序的某处实际需要结果的时候 **执行** 这些代码。这正是闭包的用武之地!
我们希望在 `generate_workout` 中只引用 `simulated_expensive_calculation` 一次,并推迟复杂计算的执行直到我们确实需要结果的时候。这正是闭包的用武之地!
#### 重构使用闭包储存代码
@ -161,15 +84,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
# expensive_closure(5);
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-05/src/main.rs:here}}
```
<span class="caption">示例 13-5定义一个闭包并储存到变量 `expensive_closure`</span>
@ -185,41 +100,12 @@ let expensive_closure = |num| {
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
if intensity < 25 {
println!(
"Today, do {} pushups!",
expensive_closure(intensity)
);
println!(
"Next, do {} situps!",
expensive_closure(intensity)
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_closure(intensity)
);
}
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-06/src/main.rs:here}}
```
<span class="caption">示例 13-6调用定义的 `expensive_closure`</span>
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行该代码。
现在如何执行复杂计算只被定义了一次,并只会在需要结果的时候执行该代码。
然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。我们稍后会讨论这个方案,不过目前让我们首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
@ -236,14 +122,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs:here}}
```
<span class="caption">示例 13-7为闭包的参数和返回值增加可选的类型注解</span>
@ -257,33 +136,22 @@ let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
```
第一行展示了一个函数定义,而第二行展示了一个完整标注的闭包定义。第三行闭包定义中省略了类型注解,而第四行去掉了可选的大括号,因为闭包体只有一行。这些都是有效的闭包定义,并在调用时产生相同的行为。
第一行展示了一个函数定义,而第二行展示了一个完整标注的闭包定义。第三行闭包定义中省略了类型注解,而第四行去掉了可选的大括号,因为闭包体只有一行。这些都是有效的闭包定义,并在调用时产生相同的行为。调用闭包要求 `add_one_v3``add_one_v4` 必须更够编译因为会根据其用途推断其类型。
闭包定义会为每个参数和返回值推断一个具体类型。例如,示例 13-8 中展示了仅仅将参数作为返回值的简短的闭包定义。除了作为示例的目的这个闭包并不是很实用。注意其定义并没有增加任何类型注解:如果尝试调用闭包两次,第一次使用 `String` 类型作为参数而第二次使用 `u32`,则会得到一个错误:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs:here}}
```
<span class="caption">示例 13-8尝试调用一个被推断为两个不同类型的闭包</span>
编译器给出如下错误:
```text
error[E0308]: mismatched types
--> src/main.rs
|
| let n = example_closure(5);
| ^ expected struct `std::string::String`, found
integer
|
= note: expected type `std::string::String`
found type `{integer}`
```console
{{#include ../listings/ch13-functional-features/listing-13-08/output.txt}}
```
第一次使用 `String` 值调用 `example_closure` 时,编译器推断 `x` 和此闭包返回值的类型为 `String`。接着这些类型被锁定进闭包 `example_closure` 中,如果尝试对同一闭包使用不同类型则会得到类型错误。
@ -305,12 +173,7 @@ error[E0308]: mismatched types
<span class="filename">文件名: src/main.rs</span>
```rust
struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>,
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs:here}}
```
<span class="caption">示例 13-9定义一个 `Cacher` 结构体来在 `calculation` 中存放闭包并在 `value` 中存放 Option 值</span>
@ -326,34 +189,7 @@ struct Cacher<T>
<span class="filename">文件名: src/main.rs</span>
```rust
# struct Cacher<T>
# where T: Fn(u32) -> u32
# {
# calculation: T,
# value: Option<u32>,
# }
#
impl<T> Cacher<T>
where T: Fn(u32) -> u32
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}}
```
<span class="caption">示例 13-10`Cacher` 的缓存逻辑</span>
@ -371,65 +207,7 @@ impl<T> Cacher<T>
<span class="filename">文件名: src/main.rs</span>
```rust
# use std::thread;
# use std::time::Duration;
#
# struct Cacher<T>
# where T: Fn(u32) -> u32
# {
# calculation: T,
# value: Option<u32>,
# }
#
# impl<T> Cacher<T>
# where T: Fn(u32) -> u32
# {
# fn new(calculation: T) -> Cacher<T> {
# Cacher {
# calculation,
# value: None,
# }
# }
#
# fn value(&mut self, arg: u32) -> u32 {
# match self.value {
# Some(v) => v,
# None => {
# let v = (self.calculation)(arg);
# self.value = Some(v);
# v
# },
# }
# }
# }
#
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_result = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!(
"Today, do {} pushups!",
expensive_result.value(intensity)
);
println!(
"Next, do {} situps!",
expensive_result.value(intensity)
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_result.value(intensity)
);
}
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}}
```
<span class="caption">示例 13-11`generate_workout` 函数中利用 `Cacher` 结构体来抽象出缓存逻辑</span>
@ -445,25 +223,15 @@ fn generate_workout(intensity: u32, random_number: u32) {
第一个问题是 `Cacher` 实例假设对于 `value` 方法的任何 `arg` 参数值总是会返回相同的值。也就是说,这个 `Cacher` 的测试会失败:
```rust,ignore,panics
#[test]
fn call_with_different_values() {
let mut c = Cacher::new(|a| a);
let v1 = c.value(1);
let v2 = c.value(2);
assert_eq!(v2, 2);
}
{{#rustdoc_include ../listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs:here}}
```
这个测试使用返回传递给它的值的闭包创建了一个新的 `Cacher` 实例。使用为 1 的 `arg` 和为 2 的 `arg` 调用 `Cacher` 实例的 `value` 方法,同时我们期望使用为 2 的 `arg` 调用 `value` 会返回 2。
使用示例 13-9 和示例 13-10 的 `Cacher` 实现运行测试,它会在 `assert_eq!` 失败并显示如下信息:
```text
thread 'call_with_different_values' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `2`', src/main.rs
```console
{{#include ../listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt}}
```
这里的问题是第一次使用 1 调用 `c.value``Cacher` 实例将 `Some(1)` 保存进 `self.value`。在这之后,无论传递什么值调用 `value`,它总是会返回 1。
@ -481,15 +249,7 @@ thread 'call_with_different_values' panicked at 'assertion failed: `(left == rig
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/main.rs}}
```
<span class="caption">示例 13-12一个引用了其周围作用域中变量的闭包示例</span>
@ -501,26 +261,13 @@ fn main() {
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool { z == x }
let y = 4;
assert!(equal_to_x(y));
}
{{#rustdoc_include ../listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs}}
```
这会得到一个错误:
```text
error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
} closure form instead
--> src/main.rs
|
4 | fn equal_to_x(z: i32) -> bool { z == x }
| ^
```console
{{#include ../listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt}}
```
编译器甚至会提示我们这只能用于闭包!
@ -537,38 +284,20 @@ error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 `move` 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
> 注意:即使其捕获的值已经被移动了,`move` 闭包仍需要实现 `Fn``FnMut`。这是因为闭包所实现的 trait 是由闭包所捕获了什么值而不是如何捕获所决定的。而 `move` 关键字仅代表了后者。
第十六章讨论并发时会展示更多 `move` 闭包的例子,不过现在这里修改了示例 13-12 中的代码(作为演示),在闭包定义中增加 `move` 关键字并使用 vector 代替整型,因为整型可以被拷贝而不是移动;注意这些代码还不能编译:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
{{#rustdoc_include ../listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs}}
```
这个例子并不能编译,会产生以下错误:
```text
error[E0382]: use of moved value: `x`
--> src/main.rs:6:40
|
4 | let equal_to_x = move |z| z == x;
| -------- value moved (into closure) here
5 |
6 | println!("can't use x here: {:?}", x);
| ^ value used here after move
|
= note: move occurs because `x` has type `std::vec::Vec<i32>`, which does not
implement the `Copy` trait
```console
{{#include ../listings/ch13-functional-features/no-listing-03-move-closures/output.txt}}
```
`x` 被移动进了闭包,因为闭包使用 `move` 关键字定义。接着闭包获取了 `x` 的所有权,同时 `main` 就不再允许在 `println!` 语句中使用 `x` 了。去掉 `println!` 即可修复问题。

@ -2,16 +2,14 @@
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md)
> <br>
> commit 8edf0457ab571b375b87357e1353ae0dd2127abe
> commit f207d4125b28f51a9bd962b3232cdea925660073
迭代器模式允许你对一个序列的项进行某些处理。**迭代器***iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
在 Rust 中,迭代器是 **惰性的***lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
```rust
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/main.rs:here}}
```
<span class="caption">示例 13-13创建一个迭代器</span>
@ -21,13 +19,7 @@ let v1_iter = v1.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);
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}}
```
<span class="caption">示例 13-14在一个 `for` 循环中使用迭代器</span>
@ -58,20 +50,11 @@ pub trait Iterator {
<span class="filename">文件名: src/lib.rs</span>
```rust
#[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);
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/lib.rs:here}}
```
<span class="caption">示例 13-15在迭代器上直接调用 `next` 方法</span>
注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**consume或使用了迭代器。每一个 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。
@ -86,17 +69,8 @@ fn iterator_demonstration() {
<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);
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs:here}}
```
<span class="caption">示例 13-16调用 `sum` 方法获取迭代器所有项的总和</span>
@ -112,24 +86,15 @@ fn iterator_sum() {
<span class="filename">文件名: src/main.rs</span>
```rust,not_desired_behavior
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-17/src/main.rs:here}}
```
<span class="caption">示例 13-17调用迭代器适配器 `map` 来创建一个新迭代器</span>
得到的警告是:
```text
warning: unused `std::iter::Map` which must be used: iterator adaptors are lazy
and do nothing unless consumed
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_must_use)] on by default
```console
{{#include ../listings/ch13-functional-features/listing-13-17/output.txt}}
```
示例 13-17 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。
@ -141,11 +106,7 @@ and do nothing unless consumed
<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]);
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}}
```
<span class="caption">示例 13-18调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector</span>
@ -160,37 +121,8 @@ assert_eq!(v2, vec![2, 3, 4]);
<span class="filename">文件名: src/lib.rs</span>
```rust
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> 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") },
]
);
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}}
```
<span class="caption">示例 13-19使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span>
@ -213,16 +145,8 @@ fn filters_by_size() {
<span class="filename">文件名: src/lib.rs</span>
```rust
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-20/src/lib.rs}}
```
<span class="caption">示例 13-20定义 `Counter` 结构体和一个创建 `count` 初值为 0 的 `Counter` 实例的 `new` 函数</span>
@ -233,24 +157,8 @@ impl Counter {
<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
}
}
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-21/src/lib.rs:here}}
```
<span class="caption">示例 13-21`Counter` 结构体上实现 `Iterator` trait</span>
@ -265,36 +173,8 @@ impl Iterator for Counter {
<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);
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}}
```
<span class="caption">示例 13-22测试 `next` 方法实现的功能</span>
@ -309,42 +189,8 @@ fn calling_next_directly() {
<span class="filename">文件名: src/lib.rs</span>
```rust
# struct Counter {
# count: u32,
# }
#
# impl Counter {
# fn new() -> Counter {
# Counter { count: 0 }
# }
# }
#
# impl Iterator for Counter {
# // 迭代器会产生 u32s
# type Item = u32;
#
# fn next(&mut self) -> Option<Self::Item> {
# // count 自增 1。也就是为什么从 0 开始。
# self.count += 1;
#
# // 检测是否结束结束计数。
# 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);
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-23/src/lib.rs:here}}
```
<span class="caption">示例 13-23使用自定义的 `Counter` 迭代器的多种方法</span>

@ -2,7 +2,7 @@
> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/main/src/ch13-03-improving-our-io-project.md)
> <br>
> commit 6555fb6c805fbfe7d0961980991f8bca6918928f
> commit cc958ca579816ea6ac7e9067d628b0423a1ed3e4
有了这些关于迭代器的新知识,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。
@ -13,20 +13,7 @@
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config { query, filename, case_sensitive })
}
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-12-23-reproduced/src/lib.rs:ch13}}
```
<span class="caption">示例 13-24重现第十二章结尾的 `Config::new` 函数</span>
@ -46,31 +33,15 @@ impl Config {
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn main() {
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--
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-12-24-reproduced/src/main.rs:ch13}}
```
修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。在更新 `Config::new` 之前这些代码还不能编译:
<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--
}
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-25/src/main.rs:here}}
```
<span class="caption">示例 13-25`env::args` 的返回值传递给 `Config::new`</span>
@ -81,51 +52,24 @@ fn main() {
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
impl Config {
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
// --snip--
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-26/src/lib.rs:here}}
```
<span class="caption">示例 13-26以迭代器作为参数更新 `Config::new` 的签名
`env::args` 函数的标准库文档显示,它返回的迭代器的类型为 `std::env::Args`。我们已经更新了 `Config :: new` 函数的签名,因此参数 `args` 的类型为 `std::env::Args` 而不是 `&[String]`。因为我们拥有 `args` 的所有权,并且将通过对其进行迭代来改变 `args` ,所以我们可以将 `mut` 关键字添加到 `args` 参数的规范中以使其可变。
现在我们还需指定字符串 slice 错误类型只能有 `'static` 生命周期。因为我们之前只会返回字符串 slice所以这是成立的。然而当参数中有一个引用的时候返回类型的引用有可能与参数的引用有着相同的生命周期。之前第十章 [“生命周期省略”][lifetime-elision] 部分讨论的规则生效,因此无需注明 `&str` 的生命周期。随着对 `args` 的修改,生命周期省略规则不再适用,所以必须指定 `'static` 生命周期。
#### 使用 `Iterator` trait 代替索引
接下来,我们将修改 `Config::new` 的内容。标准库文档还提到 `std::env::Args` 实现了 `Iterator` trait因此我们知道可以对其调用 `next` 方法!示例 13-27 更新了示例 12-23 中的代码,以使用 `next` 方法:
<span class="filename">文件名: src/lib.rs</span>
```rust
# fn main() {}
# use std::env;
#
# struct Config {
# query: String,
# filename: String,
# case_sensitive: bool,
# }
#
impl Config {
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let filename = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file name"),
};
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config { query, filename, case_sensitive })
}
}
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-27/src/lib.rs:here}}
```
<span class="caption">示例 13-27修改 `Config::new` 的函数体来使用迭代器方法</span>
@ -139,17 +83,7 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13
<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
}
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:ch13}}
```
<span class="caption">示例 13-28示例 12-19 中 `search` 函数的定义</span>
@ -159,11 +93,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(query))
.collect()
}
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-29/src/lib.rs:here}}
```
<span class="caption">示例 13-29`search` 函数实现中使用迭代器适配器</span>
@ -173,3 +103,5 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
接下来的逻辑问题就是在代码中应该选择哪种风格:是使用示例 13-28 中的原始实现还是使用示例 13-29 中使用迭代器的版本?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector迭代器代码则更关注循环的目的。这抽象掉那些老生常谈的代码这样就更容易看清代码所特有的概念比如迭代器中每个元素必须面对的过滤条件。
不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。
[lifetime-elision]: ch10-03-lifetime-syntax.html#生命周期省略lifetime-elision

@ -2,7 +2,9 @@
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/main/src/ch13-04-performance.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 009fffa4580ffb175f1b8470b5b12e4a63d670e4
为了决定使用循环还是迭代器,我们需要指导哪个实现更快一些:是显式循环版本的 `search` 函数还是迭代器版本的。
为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快一些:是直接使用 `for` 循环的版本还是使用迭代器的版本。

Loading…
Cancel
Save