From 0635f0255368845c4e84f333d4b75551291e24ea Mon Sep 17 00:00:00 2001 From: KaiserY Date: Wed, 9 Feb 2022 18:59:22 +0800 Subject: [PATCH] update to ch13-04 --- src/ch13-01-closures.md | 329 +++--------------------- src/ch13-02-iterators.md | 198 ++------------ src/ch13-03-improving-our-io-project.md | 98 ++----- src/ch13-04-performance.md | 4 +- 4 files changed, 69 insertions(+), 560 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 790c33d..e88c021 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -2,7 +2,7 @@ > [ch13-01-closures.md](https://github.com/rust-lang/book/blob/main/src/ch13-01-closures.md) >
-> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493 +> commit 8acef6cfd40a36be60a3c62458d9f78e2427e190 Rust 的 **闭包**(*closures*)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获调用者作用域中的值。我们将展示闭包的这些功能如何复用代码和自定义行为。 @@ -17,14 +17,7 @@ Rust 的 **闭包**(*closures*)是可以保存进变量或作为参数传递 文件名: src/main.rs ```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}} ``` 示例 13-1:一个用来代替假定计算的函数,它大约会执行两秒钟 @@ -41,16 +34,7 @@ fn simulated_expensive_calculation(intensity: u32) -> u32 { 文件名: src/main.rs ```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}} ``` 示例 13-2:`main` 函数包含了用于 `generate_workout` 函数的模拟用户输入和模拟随机数输入 @@ -62,36 +46,7 @@ fn main() { 文件名: src/main.rs ```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}} ``` 示例 13-3:程序的业务逻辑,它根据输入并调用 `simulated_expensive_calculation` 函数来打印出健身计划 @@ -113,46 +68,14 @@ fn generate_workout(intensity: u32, random_number: u32) { 文件名: src/main.rs ```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}} ``` 示例 13-4:将 `simulated_expensive_calculation` 调用提取到一个位置,并将结果储存在变量 `expensive_result` 中 这个修改统一了 `simulated_expensive_calculation` 调用并解决了第一个 `if` 块中不必要的两次调用函数的问题。不幸的是,现在所有的情况下都需要调用函数并等待结果,包括那个完全不需要这一结果的内部 `if` 块。 -我们希望能够在程序的一个位置指定某些代码,并只在程序的某处实际需要结果的时候 **执行** 这些代码。这正是闭包的用武之地! +我们希望在 `generate_workout` 中只引用 `simulated_expensive_calculation` 一次,并推迟复杂计算的执行直到我们确实需要结果的时候。这正是闭包的用武之地! #### 重构使用闭包储存代码 @@ -161,15 +84,7 @@ fn generate_workout(intensity: u32, random_number: u32) { 文件名: src/main.rs ```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}} ``` 示例 13-5:定义一个闭包并储存到变量 `expensive_closure` 中 @@ -185,41 +100,12 @@ let expensive_closure = |num| { 文件名: src/main.rs ```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}} ``` 示例 13-6:调用定义的 `expensive_closure` -现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行该代码。 +现在如何执行复杂计算只被定义了一次,并只会在需要结果的时候执行该代码。 然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。我们稍后会讨论这个方案,不过目前让我们首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。 @@ -236,14 +122,7 @@ fn generate_workout(intensity: u32, random_number: u32) { 文件名: src/main.rs ```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}} ``` 示例 13-7:为闭包的参数和返回值增加可选的类型注解 @@ -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`,则会得到一个错误: 文件名: src/main.rs ```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}} ``` 示例 13-8:尝试调用一个被推断为两个不同类型的闭包 编译器给出如下错误: -```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 文件名: src/main.rs ```rust -struct Cacher - where T: Fn(u32) -> u32 -{ - calculation: T, - value: Option, -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs:here}} ``` 示例 13-9:定义一个 `Cacher` 结构体来在 `calculation` 中存放闭包并在 `value` 中存放 Option 值 @@ -326,34 +189,7 @@ struct Cacher 文件名: src/main.rs ```rust -# struct Cacher -# where T: Fn(u32) -> u32 -# { -# calculation: T, -# value: Option, -# } -# -impl Cacher - where T: Fn(u32) -> u32 -{ - fn new(calculation: T) -> Cacher { - 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}} ``` 示例 13-10:`Cacher` 的缓存逻辑 @@ -371,65 +207,7 @@ impl Cacher 文件名: src/main.rs ```rust -# use std::thread; -# use std::time::Duration; -# -# struct Cacher -# where T: Fn(u32) -> u32 -# { -# calculation: T, -# value: Option, -# } -# -# impl Cacher -# where T: Fn(u32) -> u32 -# { -# fn new(calculation: T) -> Cacher { -# 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}} ``` 示例 13-11:在 `generate_workout` 函数中利用 `Cacher` 结构体来抽象出缓存逻辑 @@ -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 文件名: src/main.rs ```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}} ``` 示例 13-12:一个引用了其周围作用域中变量的闭包示例 @@ -501,26 +261,13 @@ fn main() { 文件名: src/main.rs ```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 代替整型,因为整型可以被拷贝而不是移动;注意这些代码还不能编译: 文件名: src/main.rs ```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`, 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!` 即可修复问题。 diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 3d1857b..50b032e 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -2,16 +2,14 @@ > [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md) >
-> 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}} ``` 示例 13-13:创建一个迭代器 @@ -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}} ``` 示例 13-14:在一个 `for` 循环中使用迭代器 @@ -58,20 +50,11 @@ pub trait Iterator { 文件名: src/lib.rs -```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}} ``` + 示例 13-15:在迭代器上(直接)调用 `next` 方法 注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**(consume)了,或使用了迭代器。每一个 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。 @@ -86,17 +69,8 @@ fn iterator_demonstration() { 文件名: 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); -} +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs:here}} ``` 示例 13-16:调用 `sum` 方法获取迭代器所有项的总和 @@ -112,24 +86,15 @@ fn iterator_sum() { 文件名: src/main.rs ```rust,not_desired_behavior -let v1: Vec = vec![1, 2, 3]; - -v1.iter().map(|x| x + 1); +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-17/src/main.rs:here}} ``` 示例 13-17:调用迭代器适配器 `map` 来创建一个新迭代器 得到的警告是: -```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 文件名: 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]); +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}} ``` 示例 13-18:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector @@ -160,37 +121,8 @@ assert_eq!(v2, vec![2, 3, 4]); 文件名: src/lib.rs -```rust -#[derive(PartialEq, Debug)] -struct Shoe { - size: u32, - style: String, -} - -fn shoes_in_my_size(shoes: Vec, shoe_size: u32) -> 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") }, - ] - ); -} +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}} ``` 示例 13-19:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包 @@ -213,16 +145,8 @@ fn filters_by_size() { 文件名: src/lib.rs -```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}} ``` 示例 13-20:定义 `Counter` 结构体和一个创建 `count` 初值为 0 的 `Counter` 实例的 `new` 函数 @@ -233,24 +157,8 @@ impl Counter { 文件名: 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 - } - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-21/src/lib.rs:here}} ``` 示例 13-21:在 `Counter` 结构体上实现 `Iterator` trait @@ -265,36 +173,8 @@ impl Iterator for Counter { 文件名: 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); -} +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}} ``` 示例 13-22:测试 `next` 方法实现的功能 @@ -309,42 +189,8 @@ fn calling_next_directly() { 文件名: src/lib.rs -```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 { -# // 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}} ``` 示例 13-23:使用自定义的 `Counter` 迭代器的多种方法 diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index ee38d1c..bc8338f 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -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) >
-> commit 6555fb6c805fbfe7d0961980991f8bca6918928f +> commit cc958ca579816ea6ac7e9067d628b0423a1ed3e4 有了这些关于迭代器的新知识,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。 @@ -13,20 +13,7 @@ 文件名: src/lib.rs ```rust,ignore -impl Config { - pub fn new(args: &[String]) -> Result { - 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}} ``` 示例 13-24:重现第十二章结尾的 `Config::new` 函数 @@ -46,31 +33,15 @@ impl Config { 文件名: src/main.rs ```rust,ignore -fn main() { - let args: Vec = 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` 之前这些代码还不能编译: 文件名: 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-- -} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-25/src/main.rs:here}} ``` 示例 13-25:将 `env::args` 的返回值传递给 `Config::new` @@ -81,51 +52,24 @@ fn main() { 文件名: src/lib.rs -```rust,ignore -impl Config { - pub fn new(mut args: std::env::Args) -> Result { - // --snip-- +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-26/src/lib.rs:here}} ``` 示例 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` 方法: 文件名: src/lib.rs -```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 { - 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}} ``` 示例 13-27:修改 `Config::new` 的函数体来使用迭代器方法 @@ -139,17 +83,7 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13 文件名: 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 -} +{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:ch13}} ``` 示例 13-28:示例 12-19 中 `search` 函数的定义 @@ -159,11 +93,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { 文件名: src/lib.rs ```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}} ``` 示例 13-29:在 `search` 函数实现中使用迭代器适配器 @@ -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 diff --git a/src/ch13-04-performance.md b/src/ch13-04-performance.md index c27adcb..41cea2b 100644 --- a/src/ch13-04-performance.md +++ b/src/ch13-04-performance.md @@ -2,7 +2,9 @@ > [ch13-04-performance.md](https://github.com/rust-lang/book/blob/main/src/ch13-04-performance.md) >
-> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f +> commit 009fffa4580ffb175f1b8470b5b12e4a63d670e4 + +为了决定使用循环还是迭代器,我们需要指导哪个实现更快一些:是显式循环版本的 `search` 函数还是迭代器版本的。 为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快一些:是直接使用 `for` 循环的版本还是使用迭代器的版本。