diff --git a/projects/iterator_demo/src/lib.rs b/projects/iterator_demo/src/lib.rs index bea2d9c..edd9bbf 100644 --- a/projects/iterator_demo/src/lib.rs +++ b/projects/iterator_demo/src/lib.rs @@ -10,26 +10,3 @@ fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { #[cfg(test)] mod tests; - -#[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); -} - -#[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); -} diff --git a/projects/iterator_demo/src/main.rs b/projects/iterator_demo/src/main.rs index 00097c5..7adb478 100644 --- a/projects/iterator_demo/src/main.rs +++ b/projects/iterator_demo/src/main.rs @@ -1,7 +1,9 @@ fn main() { let v1 = vec! [1, 2, 3]; - let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + let v1_iter = v1.iter(); - assert_eq! (v2, vec! [2, 3, 4]); + for val in v1_iter { + println! ("得到:{val}"); + } } diff --git a/projects/iterator_demo/src/tests.rs b/projects/iterator_demo/src/tests.rs index bbdc376..80ef3eb 100644 --- a/projects/iterator_demo/src/tests.rs +++ b/projects/iterator_demo/src/tests.rs @@ -1,5 +1,28 @@ use super::*; +#[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); +} + +#[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); +} + #[test] fn filter_by_size() { let shoes = vec! [ diff --git a/projects/iterators/Cargo.toml b/projects/iterators/Cargo.toml new file mode 100644 index 0000000..4db0b0f --- /dev/null +++ b/projects/iterators/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "iterators" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/projects/iterators/src/main.rs b/projects/iterators/src/main.rs new file mode 100644 index 0000000..00097c5 --- /dev/null +++ b/projects/iterators/src/main.rs @@ -0,0 +1,7 @@ +fn main() { + let v1 = vec! [1, 2, 3]; + + let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + + assert_eq! (v2, vec! [2, 3, 4]); +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0d43cb9..2be0420 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -90,7 +90,7 @@ - [函数式语言特性:迭代器与闭包](Ch13_Functional_Language_Features_Iterators_and_Closures.md) - [闭包](functional_features/closures.md) - - [在迭代器下处理一序列项目](functional_features/iterators.md) + - [以迭代器处理一序列项目](functional_features/iterators.md) - [改进咱们的 I/O 项目](functional_features/improving_io_project.md) - [性能比较:循环与迭代器](functional_features/performance.md) diff --git a/src/functional_features/iterators.md b/src/functional_features/iterators.md index 3cd7d18..9518ada 100644 --- a/src/functional_features/iterators.md +++ b/src/functional_features/iterators.md @@ -1,43 +1,43 @@ # 以迭代器处理一系列项目 -迭代器模式,the iterator pattern, 实现了在条目序列上,依次执行某个任务。迭代器负责对各个条目遍历,及判断序列何时结束的逻辑。咱们运用了迭代器后,就不必自己再实现那个逻辑。 +迭代器模式允许咱们依次对一系列项目执行某些任务。所谓迭代器,负责遍历每个项目以及判断序列何时结束的逻辑。当咱们使用迭代器时,咱们不必自己重新实现该逻辑。 -在 Rust 中,迭代器是 *惰性的,lazy*,这意味着在咱们调用消费该迭代器的方法,将其用完前,他们没有任何效果。例如,下面清单 13-10 中的代码,通过调用定义在 `Vec` 上的 `iter` 方法,而在矢量 `v1` 中的项目上创建了一个迭代器。这段代码本身并不做任何有用的事情。 +在 Rust 中,迭代器是 *懒惰的*,这意味着在咱们调用消费迭代器的方法将其用尽之前,他们不会产生任何效果。例如,下面清单 13-10 中的代码通过调用定义在 `Vec` 上的 `iter` 方法,创建一个遍历矢量值 `v1` 中的项目的迭代器。这段代码本身并不会完成任何有用的事情。 + ```rust let v1 = vec! [1, 2, 3]; + let v1_iter = v1.iter(); ``` -*清单 13-10:创建迭代器* +**清单 13-10**:创建迭代器 -迭代器被存储在变量 `v1_iter` 中。一旦咱们已创建出迭代器,就能以多种方式使用他。在第 3 章中的清单 3-5 中,就曾使用了 `for` 循环,对数组进行迭代,而在该数组的各个条目上执行代码。在使用 `for` 循环表象下,便是隐式地创建出迭代器,并随后消费迭代器,但直到现在,我们都未提及其原理。 +迭代器存储在变量 `v1_iter` 中。一旦我们创建了迭代器,就可以多种方式使用他。在 [清单 3-5](../programming_concepts/control_flow.md#listing_3-5) 中,我们使用 `for` 循环遍历了一个数组,以对其每个项目执行一些代码。在底层,这隐式地创建然后消费了一个迭代器,但直到现在,我们才详细探讨其工作原理。 -下面清单 13-11 中的示例里,咱们把迭代器的创建,与 `for` 循环中迭代器的使用分离开了。当使用 `v1_iter` 中的迭代器,调用 `for` 循环时,迭代器中的各个元素,就会在循环的每次迭代中被使用,这就打印出了各个值。 +下面清单 13-11 中的示例中,我们把迭代器的创建与 `for` 循环中的使用分离。当使用 `v1_iter` 中的迭代器调用 `for` 循环时,迭代器中的每个元素就会在循环的一次迭代中使用,从而打印出每个值。 + ```rust let v1 = vec! [1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { - println! ("得到了:{}", val); + println! ("得到:{val}"); } ``` -*清单 13-11:于 `for` 循环中使用迭代器* +**清单 13-11**:在 `for` 循环中使用迭代器 -在不具备由其标准库提供的迭代器的编程语言中,咱们很可能通过从 `0` 开始开始一个索引变量,使用那个变量索引到矢量值中来获取到一个值,并在循环中对递增索引变量值,直到索引变量达到矢量条目总数为止,而编写出这个同样功能。 +在没有标准库提供的迭代器的语言中,咱们通常需要通过开始一个索引 0 处的变量,使用这个变量索引进入矢量值中来获取一个值,然后在循环中递增该变量值,直到其达到矢量中的条目总数,来编写这一相同功能。 -迭代器为你处理所有这些逻辑,减少了咱们可能会搞砸的重复性代码。不只咱们可以索引的数据结构,比如矢量值,对于许多不同类别的序列,迭代器都给了我们运用同样逻辑的更多灵活性。咱们来看看迭代器是如何做到的。 +迭代器为咱们处理了所有这些逻辑,从而减少咱们可能搞砸的重复代码。迭代器给予咱们更大的灵活性,以对许多不同类别的序列使用这同一逻辑,而不仅仅像矢量值那样咱们可以索引的数据结构。我们来看看迭代器是如何做到这一点的。 ## `Iterator` 特质与 `next` 方法 -**The `Iterator` Trait and the `next` Method** - - -所有迭代器都实现了在标准库中定义的名为 `Iterator` 的特质。该特质的定义看起来像这样: +所有迭代器都实现了定义在标准库中的名为 `Iterator` 的特质。该特质的定义看起来像下面这样: ```rust @@ -46,16 +46,17 @@ pub trait Iterator { fn next(&mut self) -> Option; - // 这里省略了有着默认实现的方法 + // 有着默认实现的方法已省略 } ``` -请注意此定义使用了一些新语法:`type Item` 与 `Self::Item`,他们定义着此特质下的一个 *关联类型,associated type*。在 19 章中,咱们将深入谈及关联类型。至于现在,咱们只需清楚这段代码表明,实现 `Iterator` 特质需要咱们同时定义一个 `Item` 类型,而这个 `Item` 类型会在 `next` 方法返回值类型中用到。也就是说,`Item` 类型将是迭代器返回的类型。 +请注意,这个定义使用了一些新语法:`type Item` 与 `Self::Item`,他们定义了这个特质下的 *关联类型*。我们将在第 20 章中深入探讨关联类型。目前咱们只需知道,这段代码表明实现 `Iterator` 特质需要咱们同时定义一个 `Item` 类型,而这个 `Item` 类型会用于 `next` 方法的返回类型中。换言之,`Item` 类型将是自迭代器返回的类型。 -`Iterator` 特质只需要实现者,implementors,定义一个方法:即 `next` 方法,该方法会一次返回一个封装在 `Some` 中的迭代器条目,当迭代完毕时,就会返回 `None`。 +`Iterator` 特质仅要求实现者定义一个方法:`next` 方法,他会一次返回迭代器的一个项目,包装在 `Some` 中,当迭代完毕时,返回 `None`。 -咱们可以直接调用迭代器上的 `next` 方法;下面清单 13-12 演示了,在自矢量创建出的迭代器上,反复调用 `next` 方法,会返回的值。 +我们可以直接调用对迭代器调用 `next` 方法;下面清单 13-12 演示了对从矢量创建的迭代器反复调用 `next` 会返回哪些值。 + 文件名:`src/lib.rs` ```rust @@ -72,23 +73,20 @@ fn iterator_demonstration() { } ``` -*清单 13-12:调用迭代器上的 `next` 方法* +**清单 13-12**:对迭代器调用 `next` 方法 +请注意,我们需要构造 `v1_iter` 为可变:调用迭代器上的 `next` 方法会修改迭代器的内部状态,迭代器用于追踪其位于序列中何处。换句话说,这段代码 *消费* 或用完迭代器。每次调用 `next` 都会吃掉迭代器中的一个项目。当我们使用 `for` 循环时,我们不需要使 `v1_iter` 可变,因为循环取得了 `v1_iter` 的所有权并在幕后使其可变。 -请注意咱们需将 `v1_iter` 构造为可变:调用迭代器上的 `next` 方法,会修改迭代器用来追踪其位于序列中何处的内部状态。换句话说,这段代码 *消费,consumes*,或用掉,use up,了迭代器。每次对 `next` 的调用,都会吃掉迭代器的一个条目。在咱们使用 `for` 循环时,之所以不需要将 `v1_iter` 构造为可变,是由于那个循环取得了 `v1_iter` 的所有权,而在幕后将其构造为了可变。 - -还要注意咱们从 `next` 的调用获取到值,都是到矢量中值的不可变引用。`iter` 方法会产生对不可变引用的迭代器。若咱们打算创建出取得 `v1` 所有权,并返回有所有权的数据时,咱们可以调用 `into_iter` 而非 `iter`。与此类似,若咱们打算对可变引用迭代,咱们可以调用 `iter_mut` 而非 `iter`。 +还要注意,我们从调用 `next` 获取的值,属于对矢量中的值的不可变引用。`iter` 方法会生成一个对不可变引用的迭代器。当我们打算创建一个会取得 `v1` 的所有权并返回自有的值时,我们可以调用 `into_iter` 而不是 `iter`。同样的,当我们打算遍历可变引用时,可以调用 `iter_mut` 而不是 `iter`。 ## 消费迭代器的方法 -**Methods that Consume the Iterator** +`Iterator` 特质有着数个带有标准库提供的默认实现的不同方法;咱们可以通过查阅 `Iterator` 特质的标准库 API 文档,了解这些方法。其中一些方法在他们的定义中调用 `next` 方法,这就是为什么在实现 `Iterator` 特质时,咱们需要实现 `next` 方法。 +调用 `next` 的方法称为 *消费适配器,consuming adaptors*,因为调用他们会耗尽迭代器。一个例子是 `sum` 方法,他会取得迭代器的所有权,并通过重复调用 `next` 方法来迭代项目,从而消费迭代器。在遍历过程中,他会把每个条目加到一个运行总和,并在迭代完成后返回总和。下面清单 13-13有个测试,演示了 `sum` 方法的用法: -`Iterator` 特质有着数个不同的,带有由标准库提供默认实现的方法;通过查阅 `Iterator` 特质的标准库 API 文档,咱们便可找到这些方法。其中一些方法,在他们的定义中会调用 `next` 方法,这就是为什么在实现 `Iterator` 特质时需要实现 `next` 方法的原因。 - -调用 `next` 的方法称为 *消费适配器,consuming adaptors*,因为调用它们会耗尽迭代器。一个例子是 `sum` 方法,他会获取迭代器的所有权并通过重复调用 `next` 方法来迭代项目,从而消费迭代器。在迭代过程中,他会把每个条目,加到一个运行中的总和,并在遍历完成时返回总和。下面清单 13-13,有着说明 `sum` 方法运用的测试: - + 文件名:`src/lib.rs` ```rust @@ -104,20 +102,19 @@ fn iterator_sum() { } ``` -*清单 13-13:调用 `sum` 方法来获取迭代器中全部项目的总和* +**清单 13-13**:调用 `sum` 方法获取迭代器中所有项目的总和 -由于 `sum` 取得了咱们于其上调用他的迭代器所有权,因此在 `sum` 的调用后,就不允许使用 `v1_iter` 了。 +我们不允许在调用 `sum` 之后再使用 `v1_iter`,因为 `sum` 取我们对其调用他的迭代器的所有权。 -## 产生出其他迭代器的方法 +## 产生其他迭代器的方法 -**Iterators that Produce Other Iterators** +所谓 *迭代器适配器,iterator adaptors*,属于一些定义在 `Iterator` 特质中的方法,他们不消费迭代器。相反,他们通过修改原始迭代器的某些方面而生成不同的迭代器。 + +下面清单 13-14 展示了调用迭代器适配器方法 `map` 的一个示例,其取一个闭包,在遍历迭代器项目时对每个项目调用该闭包。`map` 方法返回一个新的迭代器,生成修改后的项目。这里的闭包创建了一个新的迭代器,其中矢量值的每个项目都将增加 1: -*迭代器适配器,iterator adaptors* 是定义在 `Iterator` 特质上,不会消费迭代器的方法。相反,他们会通过改变初始迭代器的某一方面,而产生出另一迭代器。 - -下面清单 13-17 给出了调用迭代器适配器方法 `map` 的示例,其会取迭代器条目被遍历时,在各个条目上调用的一个闭包。`map` 方法会返回产生出修改后条目的新迭代器。这里的闭包创建了一个新的迭代器,其中原矢量的各个条目都增加了 `1`: - + 文件名:`src/main.rs` ```rust @@ -126,33 +123,38 @@ fn iterator_sum() { v1.iter().map(|x| x + 1); ``` -*清单 13-14:调用迭代器适配器 `map` 来创建出新迭代器* +**清单 13-14**:调用迭代器适配器 `map` 来创建新的迭代器 -然而,此代码会产生一条告警: +然而,这段代码产生了一条告警: ```console -$ cargo run lennyp@vm-manjaro - Compiling iterator_demo v0.1.0 (/home/lennyp/rust-lang/iterator_demo) +$ cargo run + Compiling iterators v0.1.0 (/home/hector/rust-lang-zh_CN/projects/iterators) warning: unused `Map` that must be used --> src/main.rs:4:5 | 4 | v1.iter().map(|x| x + 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `#[warn(unused_must_use)]` on by default = note: iterators are lazy and do nothing unless consumed + = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default +help: use `let _ = ...` to ignore the resulting value + | +4 | let _ = v1.iter().map(|x| x + 1); + | +++++++ -warning: `iterator_demo` (bin "iterator_demo") generated 1 warning - Finished dev [unoptimized + debuginfo] target(s) in 0.36s - Running `target/debug/iterator_demo` +warning: `iterators` (bin "iterators") generated 1 warning + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s + Running `target/debug/iterators` ``` -清单 13-14 中的代码没有做任何事情;咱们指定的闭包从未被调用。这个警告提醒了我们为什么:迭代器适配器是懒惰的,我们需要在这里消费迭代器。 +清单 13-14 中的代码未执行任何操作;我们指定的闭包从未被调用。这一告警提醒了我们原因:迭代器适配器是懒惰的,而我们需要在这里消费迭代器。 -为修正此告警并消费迭代器,咱们将使用 `collect` 方法,在第 12 章的清单 12-1 中,咱们曾对 `env::args` 用到过该方法。此方法会消费迭代器,并将结果值收集到一个集合数据类型中。 +为了解决这一告警并消费该迭代器,咱们将使用 `collect` 方法,我们在 [清单 12-1](../io_project/accepting_cli_arguments.md#listing_12-1) 中对 `env::args` 使用过该方法。这个方法会消费迭代器,并收集结果值到集合数据类型中。 -下面清单 13-15 中,咱们把对从到 `map` 调用,返回的迭代器遍历的结果,收集到一个矢量值中。这个矢量最终将包含原矢量中增加 `1` 后的每一项。 +在下面清单 13-15 中,我们收集了遍历从调用 `map` 返回的迭代器的结果到一个矢量值中。这个矢量最终将包含原矢量值中的每个项目,且都增加 1。 + 文件名:`src/main.rs` ```rust @@ -163,24 +165,24 @@ warning: `iterator_demo` (bin "iterator_demo") generated 1 warning assert_eq! (v2, vec! [2, 3, 4]); ``` -*清单 13-15:调用 `map` 方法创建出新迭代器,并随后调用 `collect` 方法消费这个新的迭代器并创建出一个矢量值* +**清单 13-15**:调用 `map` 方法创建一个新的迭代器,然后调用 `collect` 方法消费新的迭代器并创建一个矢量值 -由于 `map` 取了一个闭包,因此咱们便可指定出,咱们想要对各个条目执行的任何操作。这是一个很好的例子,说明闭包如何让咱们在重用Iterator属性提供的迭代行为的同时,定制一些行为。 +由于 `map` 取一个闭包,因此我们可以指定对每个项目想要的任何操作。这是个很好的示例,展示了闭包如何让咱们在重用 `Iterator` 特质提供的迭代行为的同时,自定义某些行为。 -咱们可将多个调用,链接到迭代器适配器,来以能读懂方式执行复杂操作。但由于所有迭代器都是惰性的,因此咱们必须调用一个消费适配器方法,one of the consuming apdaptor methods,来获取调用迭代器适配器的结果。 +咱们可以链接多个调用到迭代器适配器,以可读的方式执行复杂操作。但由于所有迭代器都是懒惰的,因此咱们必须调用消费适配器方法之一,来获取迭代器适配器(方法)调用的结果。 -## 使用捕获其环境的闭包 +## 捕获环境的闭包 -**Using Closures that Capture Their Environment** +许多迭代器适配器都会取闭包作为参数,且我们将作为参数指定给迭代器适配器的闭包,往往都将是一些会捕获其环境的闭包。 +针对这一示例,我们将使用取一个闭包的 `filter` 方法。这个闭包获取一个迭代器中的项目并返回一个 `bool` 值。当闭包返回 `true` 时,值将包含在 `filter` 产出的迭代中。当闭包返回 `false` 时,值不会被包含。 -许多迭代器适配器,都会取闭包作参数,且通常咱们指定给迭代器适配器的闭包,都将是捕获其环境的闭包。 +> **译注**:原文这里有拼写错误,"If the closure returns `true`, the value will be included in the iteration produced by `filter`." 译者已通过 [Spell problem in Ch13-2 #4705](https://github.com/rust-lang/book/issues/4705) 提交该问题。 -咱们将使用取闭包的 `filter` 方法,作为这方面的示例。闭包从其所在迭代器获取到一个条目,并返回一个 `bool`。闭包返回 `true` 时,条目的值将被包含在由 `filter` 产生出的迭代中。在闭包返回 `false` 时,条目的值则不会被包含。 - -下面清单 13-16 中,咱们使用带有捕获其环境中 `shoe_size` 变量闭包的 `filter` 方法,来迭代 `Shoe` 结构体实例的集合。他将返回仅限特定尺码的鞋子。 +在下面清单 13-16 中,我们以一个闭包使用 `filter`,会捕获其环境中的 `shoe_size` 变量,以遍历 `Shoe` 结构体实例的集合。他将仅返回指定尺码的鞋子。 + 文件名:`src/lib.rs` ```rust