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

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

在 Rust 中,迭代器是惰性的,这意味着它们在你调用 使用迭代器来用完它的方法。例如,中的 示例 13-10 通过调用 在 上定义的方法。此代码本身不执行任何作 有用。v1iterVec<T>

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

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

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

在示例 13-11 的示例中,我们将迭代器的创建与 在循环中使用迭代器。当使用 中的迭代器 ,迭代器中的每个元素都用于一个 迭代,打印出每个值。forforv1_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 向量。让我们看看迭代器是如何做到这一点的。

Iterator trait 和 next Method

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

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

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

    // methods with default implementations elided
}
}

请注意,此定义使用了一些新语法:和 , 它们定义了与此 trait 关联的类型。我们将讨论 关联类型在第 19 章中深入介绍。现在,您需要知道的是 此代码表示实现 trait 还需要您定义 一个类型,并且此类型用于方法的返回类型。换句话说,type 将是从 迭 代。type ItemSelf::ItemIteratorItemItemnextItem

该 trait 只要求实现者定义一个方法: 该方法,该方法一次返回迭代器的一个项目,当迭代结束时,返回 。IteratornextSomeNone

我们可以直接在迭代器上调用该方法;示例 13-12 演示了 对创建的迭代器的重复调用返回哪些值 从向量。nextnext

文件名: 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:在迭代器上调用方法next

请注意,我们需要使 mutable:在 iterator 更改 iterator 用于跟踪 where 的内部状态 它在序列中。换句话说,此代码会消耗或用完 迭 代。每次调用 to 都会吃掉迭代器中的一个项目。我们不需要 当我们使用循环时,使 mutable 可变,因为循环采用 的所有权,并使其在幕后可变。v1_iternextnextv1_iterforv1_iter

另请注意,我们从 call 中获得的值是不可变的 对向量中值的引用。该方法生成一个迭代器 over 不可变引用。如果我们想创建一个迭代器,它采用 ownership 并返回 owned 值,我们可以调用 而不是 。同样,如果我们想迭代可变引用,我们可以调用 而不是 .nextiterv1into_iteriteriter_mutiter

使用 Iterator 的方法

该 trait 有许多不同的方法,其中 default 标准库提供的实现;您可以了解这些 方法,方法是在标准库 API 文档中查找 trait。其中一些方法在其定义中调用该方法,该 是您在实现 trait 时需要实现该方法的原因。IteratorIteratornextnextIterator

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

文件名: 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_itersumsum

生成其他迭代器的方法

Iterator adapters 是在 trait 上定义的方法,它们没有 使用 iterator。相反,它们通过更改 原始迭代器的某些方面。Iterator

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

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

    v1.iter().map(|x| x + 1);
}
示例 13-14:调用迭代器适配器以创建新的迭代器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。

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

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

文件名: 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:调用该方法以创建新的迭代器,然后调用该方法以使用新的迭代器并创建一个 vectormapcollect

因为 take 一个闭包,所以我们可以指定我们想要执行的任何作 在每个项目上。这是一个很好的例子,说明闭包如何让你自定义一些 行为,同时重用 trait 提供。mapIterator

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

使用捕获其环境的闭包

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

在此示例中,我们将使用采用闭包的方法。这 closure 从迭代器中获取一个项目并返回一个 .如果 返回 ,该值将包含在 生成的迭代中。如果 Closure 返回 ,则不会包含该值。filterbooltruefilterfalse

在示例 13-16 中,我们使用 with 一个闭包,从其环境中捕获变量来迭代 struct 的集合 实例。它将仅返回指定尺码的鞋子。filtershoe_sizeShoe

文件名: 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:使用带有闭包的方法,该闭包捕获filtershoe_size

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

在 的主体中,我们调用创建一个迭代器 ,这需要向量的所有权。然后我们调用 以调整该 iterator 转换为新的迭代器,该迭代器仅包含闭包的 返回。shoes_in_sizeinto_iterfiltertrue

闭包从环境中捕获参数,并且 将该值与每只鞋的尺码进行比较,仅保留该尺码的鞋子 指定。最后,调用 collect 由 adaptediterator 转换为函数返回的 vector。shoe_sizecollect

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

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