使用迭代器处理一系列项目

迭代器模式允许您对 转。迭代器负责迭代每个项目的逻辑,并且 确定序列何时完成。当您使用迭代器时,您不会 必须自己重新实现该逻辑。

在 Rust 中,迭代器是惰性的,这意味着它们在你调用 使用迭代器来用完它的方法。例如,中的 示例 13-10 在 vector 中的项目上创建一个迭代器v1通过调用 这itermethod defined onVec<T>.此代码本身不执行任何作 有用。

文件名: src/main.rs
fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();
}
示例 13-10:创建迭代器

迭代器存储在v1_iter变量。创建 iterator 中,我们可以以多种方式使用它。在第 3 章的示例 3-5 中,我们 使用for循环在其每个 项目。在后台,这隐式创建并使用了一个迭代器 但直到现在,我们才掩盖了它究竟是如何工作的。

在示例 13-11 的示例中,我们将迭代器的创建与 在for圈。当for循环使用 中的 iteratorv1_iter,则迭代器中的每个元素都用于一个 迭代,打印出每个值。

文件名: src/main.rs
fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Got: {val}");
    }
}
示例 13-11:在for

在没有标准库提供的迭代器的语言中, 你可能会通过在 index 处启动一个变量来编写相同的功能 0,使用该变量索引向量以获取值,以及 在循环中递增变量值,直到达到 向量中的项目。

迭代器为您处理所有这些逻辑,从而减少重复的代码 可能会搞砸。迭代器为您提供了更大的灵活性来使用相同的 logic 具有许多不同类型的序列,而不仅仅是数据结构 index into 的 like 向量。让我们看看迭代器是如何做到这一点的。

Iteratortrait 和next方法

所有迭代器都实现了一个名为Iterator,在 标准库。trait 的定义如下所示:

#![allow(unused)]
fn main() {
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;

    // methods with default implementations elided
}
}

请注意,此定义使用了一些新语法:type ItemSelf::Item, 它们定义了与此 trait 关联的类型。我们将讨论 关联类型在第 19 章中深入介绍。现在,您需要知道的是 这段代码说实现Iteratortrait 要求您还定义 一Itemtype 和 thisItemtype 用于next方法。换句话说,Itemtype 将从 迭 代。

Iteratortrait 只需要实现者定义一个方法:next方法,该方法一次返回 Iterator 的一个项目,包装在Some并且,当迭代结束时,返回None.

我们可以调用nextmethod 直接对迭代器执行;示例 13-12 演示了 重复调用next在创建的迭代器 从向量。

文件名: src/lib.rs
#[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);
    }
}
示例 13-12:调用nextmethod

请注意,我们需要将v1_itermutable:调用next方法在 iterator 更改 iterator 用于跟踪 where 的内部状态 它在序列中。换句话说,此代码会消耗或用完 迭 代。每次调用next吃掉 Iterator 中的项。我们不需要 使v1_itermutable 时,当我们使用for循环,因为循环占用了 所有权v1_iter并使其在幕后可变。

还要注意,我们从对next是不可变的 对向量中值的引用。这iter方法生成一个迭代器 over 不可变引用。如果我们想创建一个迭代器,它采用 所有权v1并返回拥有的值,我们可以调用into_iter而不是iter.同样,如果我们想迭代可变引用,我们可以调用iter_mut而不是iter.

使用 Iterator 的方法

Iteratortrait 有许多不同的方法,其中 default 标准库提供的实现;您可以了解这些 方法,方法是查看标准库 API 文档中的Iterator特性。其中一些方法调用nextmethod 的定义中,其中 是您需要实现next方法,在实现Iterator特性。

调用next称为 consuming adapters,因为调用它们 用完了迭代器。一个例子是sum方法,该方法获取 迭代器,并通过重复调用next因此 使用 iterator。当它迭代时,它会将每个项目添加到正在运行的 total 并在迭代完成时返回 total。示例 13-13 有一个 test 演示了sum方法:

文件名: src/lib.rs
#[cfg(test)]
mod tests {
    #[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-13:调用sum获取迭代器中所有项目的总数的方法

我们不允许使用v1_iter调用sum因为sum需要 我们调用它的迭代器的所有权。

生成其他迭代器的方法

Iterator adapters 是在Iterator不 使用 iterator。相反,它们通过更改 原始迭代器的某些方面。

示例 13-14 显示了调用 iterator 适配器方法的示例map, 它需要一个 Closure 来在迭代 Item 时调用每个 Item。 这mapmethod 返回生成修改后的项的新迭代器。这 this 中的 closure 创建一个新的迭代器,其中 vector 中的每个项目都将是 递增 1:

文件名: src/main.rs
fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];

    v1.iter().map(|x| x + 1);
}
示例 13-14:调用 iterator 适配器map创建新的迭代器

但是,此代码会生成警告:

$ cargo run
   Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `Map` that must be used
 --> src/main.rs:4:5
  |
4 |     v1.iter().map(|x| x + 1);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: iterators are lazy and do nothing unless consumed
  = note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
  |
4 |     let _ = v1.iter().map(|x| x + 1);
  |     +++++++

warning: `iterators` (bin "iterators") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
     Running `target/debug/iterators`

示例 13-14 中的代码什么都没做;我们指定的 从来没有被叫到。该警告提醒我们原因:iterator 适配器是惰性的,并且 我们需要在这里使用 iterator。

要修复此警告并使用迭代器,我们将使用collect方法 我们在第 12 章中使用了它env::args在示例 12-1 中。此方法 使用 iterator 并将结果值收集到集合数据中 类型。

在示例 13-15 中,我们收集了迭代器的结果,该迭代器是 从对map转换为向量。这个向量最终会得到 包含原始向量中递增 1 的每个项目。

文件名: src/main.rs
fn main() {
    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]);
}
示例 13-15:调用map方法来创建新的迭代器,然后调用collect使用新迭代器并创建 vector 的方法

因为map接受一个闭包,我们可以指定要执行的任何作 在每个项目上。这是一个很好的例子,说明闭包如何让你自定义一些 行为,同时重用迭代行为,则Iterator特性 提供。

您可以将多个调用链接到迭代器适配器,以在 一种可读的方式。但是因为所有的迭代器都是惰性的,所以你必须调用 使用 Adapter 方法从对 Iterator Adapter 的调用中获取结果。

使用捕获其环境的闭包

许多迭代器适配器将闭包作为参数,通常是闭包 我们将指定 as 参数 iterator adapters 将是捕获 他们的环境。

在此示例中,我们将使用filter方法。这 closure 从迭代器中获取一个项目并返回一个bool.如果 返回true,该值将包含在由filter.如果 closure 返回false,则不会包含该值。

在示例 13-16 中,我们使用filter使用一个闭包来捕获shoe_size变量来迭代Shoe结构 实例。它将仅返回指定尺码的鞋子。

文件名: src/lib.rs
#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe {
                    size: 10,
                    style: String::from("sneaker")
                },
                Shoe {
                    size: 10,
                    style: String::from("boot")
                },
            ]
        );
    }
}
示例 13-16:使用filter方法,其中包含一个闭包,该闭包捕获shoe_size

shoes_in_size函数获得 Shoes 和 Shoe 的向量的所有权 size 作为参数。它返回一个向量,该向量仅包含指定 大小。

在 bodyshoes_in_size,我们调用into_iter创建迭代器 ,这需要向量的所有权。然后我们调用filter以适应 iterator 转换为新的迭代器,该迭代器仅包含闭包的 返回true.

闭包捕获shoe_sizeparameter 和 将该值与每只鞋的尺码进行比较,仅保留该尺码的鞋子 指定。最后,调用collect收集 adaptediterator 转换为函数返回的 vector。

测试表明,当我们调用shoes_in_size,我们只得到鞋子 的大小与我们指定的值相同。

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准