Rust 的迭代器 II

迭代器的应用诀窍

技术
技术Rust标准库编程语言特性迭代器

2026-03-27

了解了关于迭代器的两个核心特征,以及一些基本语法后,你才只是窥见了关于 Rust 迭代器的冰山一角!下面我会介绍一系列迭代器的具体用法。

Iterator 提供的方法

Iterator 除了 next 需要我们自己编写,其他方法都提供了默认实现。它们提供很多针对迭代器进行的便捷操作。

collect
将当前迭代器转换为一个集合

消耗当前迭代器所有权,返回一个实现了 FromIterator 的集合。

因此该方法总是出现在调用链的末尾。

collect 方法极为全能的一点是,它能将迭代器转换为你选定的任何集合类型(甚至不是集合类型都可以)。但是强大的背后总有代价:你必须为它手动标注转换的类型,要么使用涡轮鱼语法,要么在结果一侧添加类型注解。

let a = [1, 2, 3];
// 在结果一侧添加类型注解 `Vec<i32>`
let doubled: Vec<i32> = a.iter().map(|x| x * 2).collect();
assert_eq!(vec![2, 4, 6], doubled);

// 使用涡轮鱼语法标注类型为 `Vec<i32>`
let doubled = a.iter().map(|x| x * 2).collect::<Vec<i32>>();
assert_eq!(vec![2, 4, 6], doubled);

// `collect` 只关心你想要生成什么类型
// 所以有时它会允许类型的一部分用 `_` 代替
let doubled = a.iter().map(|x| x * 2).collect::<Vec<_>>();
assert_eq!(vec![2, 4, 6], doubled);
cmppartial_cmp
将当前迭代器中的每个元素与另一个给定的迭代器中的元素进行比较

cmp 要求两个迭代器所迭代的元素类型相同且实现了 Ord 特征。

use std::cmp::Ordering;

assert_eq!([1].iter().cmp([1].iter()), Ordering::Equal);
assert_eq!([1].iter().cmp([1, 2].iter()), Ordering::Less);
assert_eq!([1, 2].iter().cmp([1].iter()), Ordering::Greater);

partial_cmp 则是把要求放宽到实现 PartialOrd 就行。但是遇到无法比较的情况则会返回 None

use std::cmp::Ordering;

assert_eq!([1.0, f64::NAN].iter().partial_cmp([2.0, f64::NAN].iter()), Some(Ordering::Less));
assert_eq!([2.0, f64::NAN].iter().partial_cmp([1.0, f64::NAN].iter()), Some(Ordering::Greater));
// 浮点类型的 NaN 无法比较
assert_eq!([f64::NAN, 1.0].iter().partial_cmp([f64::NAN, 2.0].iter()), None);
eqne
将当前迭代器中每个元素与另一给定的迭代器中的元素比较是否相同

要求两个迭代器所迭代的元素类型互相之间实现了 PartialEq 特征。

assert_eq!([1].iter().eq([1].iter()), true);
assert_eq!([1].iter().eq([1, 2].iter()), false);
assert_eq!([1, 2].iter().eq([1, 2].iter()), true);

assert_eq!([1].iter().ne([1].iter()), false);
assert_eq!([1].iter().ne([1, 2].iter()), true);
count
消耗迭代器返回迭代总次数(即元素总数)

此方法通过反复调用 next 来对迭代的元素进行计数,当遇到 None 时停止计数,并返回 usize 值。

由于迭代器元素总数可能超过 usize 可表示范围,所以当发生溢出时(超出 usize::MAX)程序会报错或直接崩溃。

let a = [1, 2, 3, 4, 5];
assert_eq!(a.iter().count(), 5);
last
消耗迭代器返回最后一个迭代的元素

此方法通过持续追踪当前迭代的元素,来最终返回它观察到的最后一个元素。若元素无限多,方法会崩溃。

let a = [1, 2, 3, 4, 5];
assert_eq!(a.into_iter().last(), Some(5));
nth
返回索引为 n 的元素

n 是零索引的,因此 0 代表迭代的首个元素。如果 n 超出可用索引会返回 None

要注意的是,该方法从前迭代到第 n 个元素后,除了返回第 n 个元素,还会丢弃前面的所有元素。这意味着对同一迭代器多次调用 nth 时要特别小心。

let a = [1, 2, 3];
let mut iter = a.into_iter();

// 第一次调用导致第 1 个元素被返回
// 而第 0 个元素被丢弃
assert_eq!(iter.nth(1), Some(2));
// 所以此时迭代器只剩下 1 个元素了
assert_eq!(iter.nth(1), None);

适配器

接受一个迭代器作为参数并返回另一个迭代器的函数通常被称为迭代器适配器(iterator adapters)。Iterator 提供的方法当中就有不少是迭代器适配器。

前面我们并没有谈到的一点是,迭代器是可组合的,通常会将它们链式调用来执行一连串更复杂的处理。而这恰恰是适配器的功劳。

下面介绍几种常见的迭代器适配器。

copiedcloned
将当前迭代器中的所有元素拷贝到一个新迭代器中并返回新迭代器

copied 要求原迭代器的元素是实现了 Copy 的引用类型。之所以要求元素是引用是因为很多情景下,你拥有的迭代器是引用形式的(迭代的元素是引用类型),且想从一个迭代 &T 元素的迭代器生成一个迭代 T 元素的迭代器。此方法就是为应对这种需求而生的。

let a = [1, 2, 3];

// `.iter` 使得迭代器的元素变成了 `&i32`
let v_copied: Vec<_> = a.iter().copied().collect();

// `copied` 与 `.map(|&x| x)` 效果相同
let v_map: Vec<_> = a.iter().map(|&x| x).collect();

assert_eq!(v_copied, [1, 2, 3]);
assert_eq!(v_map, [1, 2, 3]);

clonedcopied 几乎一样,最大的不同在于对元素的限制从实现 Copy 放宽到了实现 Clone

let a = [1, 2, 3];

let v_cloned: Vec<_> = a.iter().cloned().collect();

// 等价于
let v_map: Vec<_> = a.iter().map(|&x| x).collect();

assert_eq!(v_cloned, [1, 2, 3]);
assert_eq!(v_map, [1, 2, 3]);

为获得更佳的性能,最好把 cloned 放在适配器调用链的后面。

cycle
创建一个无限循环的迭代器,遍历完一遍再从头开始

生成的迭代器永远不会返回 None(除非原始迭代器为空)。

要求原迭代器实现 Clone。因为重新从原始迭代器的开头开始需要克隆整个迭代器。

let a = [1, 2, 3];

let mut iter = a.into_iter().cycle();

loop {
    assert_eq!(iter.next(), Some(1));
    assert_eq!(iter.next(), Some(2));
    assert_eq!(iter.next(), Some(3));
    assert_eq!(iter.next(), Some(1));
    break;
}
sum
对迭代器的所有元素求和

要求迭代的元素实现 Sum 特征,也就是可求和。在这一点上,甚至元素是 OptionResult 都可以求和。

let a = [1, 2, 3];
let sum: i32 = a.iter().sum();
assert_eq!(sum, 6);

let words = vec!["have", "a", "great", "day"];
// `.find` 会在字符串中寻找给定字符
// 若找到,就返回该字符在字符串中出现的索引位置
// 若没找到就返回 `None`
let total: Option<usize> = words.iter().map(|w| w.find('a')).sum();
assert_eq!(total, Some(5));
take
取出指定的最多 n 个元素组成一个新迭代器

take(n) 会持续从原迭代器取出元素,直到已取出 n 个元素或迭代器到达末尾。如果原始迭代器至少包含 n 个元素,则返回的迭代器是长度为 n 的前缀;否则返回原始迭代器中的所有(少于 n 个)元素。

let a = [1, 2, 3];

let mut iter = a.into_iter().take(2);

assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

由于 take 的此种特性,它常常与元素数无限的迭代器或无限循环的迭代器搭配使用,这样能让它们变为有限的迭代器。

// `0..` 拥有无限多个元素
let mut iter = (0..).take(3);

assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

let v = [1, 2];
// 使用 `cycle` 使其无限循环
let mut iter = v.into_iter().cycle().take(3);
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), None);

take 能与 by_ref 搭配使用,这样的话前 n 个元素被取出后,原迭代器还能正常使用。

let mut words = ["hello", "world", "of", "Rust"].into_iter();

// 取出前 2 个元素
let hello_world: Vec<_> = words.by_ref().take(2).collect();
assert_eq!(hello_world, vec!["hello", "world"]);

// 将剩余元素转换为集合
let of_rust: Vec<_> = words.collect();
assert_eq!(of_rust, vec!["of", "Rust"]);
chain
将两个迭代器拼接成一个新的迭代器

消耗两个迭代器,将它们首尾相接(self 在前,传入的迭代器在后)合成新迭代器。方法要求两个迭代器的元素类型相同。

由于该方法接收的是 IntoIterator,所以任何可转为迭代器的类型(例如 [T])都支持进行拼接。

let s1 = "ab".chars();
let s2 = "def".chars();
let mut iter = s1.chain(s2);

assert_eq!(iter.next(), Some('a'));
assert_eq!(iter.next(), Some('b'));
assert_eq!(iter.next(), Some('d'));
assert_eq!(iter.next(), Some('e'));
assert_eq!(iter.next(), Some('f'));
assert_eq!(iter.next(), None);

let a1 = [1, 2];
let a2 = [4, 5, 6];
let mut iter = a1.into_iter().chain(a2);

assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
zip
将两个迭代器合并,对应元素拼接成元组

消耗两个迭代器,将它们合并“压缩”,产生一个新迭代器。此迭代器中每个元素都是一个元组:第一个元素来自 self,第二个元素来自传入此方法的迭代器。

如果其中一个迭代器提前结束了迭代,那么合并过程也会到此戛然而止。

由于该方法接收的是 IntoIterator,所以任何可转为迭代器的类型(例如 [T])都支持进行合并“压缩”。

let s1 = "abc".chars();
let s2 = "defg".chars();
let mut iter = s1.zip(s2);

assert_eq!(iter.next(), Some(('a', 'd')));
assert_eq!(iter.next(), Some(('b', 'e')));
assert_eq!(iter.next(), Some(('c', 'f')));
// 由于 s1 提前结束了迭代
// 因此合并后的迭代器也会在此结束迭代
// `s2` 后面剩余元素不再参与合并
assert_eq!(iter.next(), None);
map
使用闭包将原迭代器每个元素映射到新迭代器中

该方法接收一个 FnMut(Self::Item) -> B 的闭包,然后方法会依次将原迭代器每个元素传入闭包,并将闭包产生的值依次放入新迭代器。

let a = [1, 2, 3];
let mut iter = a.iter().map(|x| 2 * x);

assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);

我们使用 map 是想要将一个迭代器转为另一迭代器,并在此新迭代器上继续其他操作。

如果你所传入的闭包不会使其转为另一迭代器,那么更好的选择是使用 for 循环。

// 不要写成
// (0..5).map(|x| println!("{x}"));
// 此语句甚至不会执行,因为 `map` 是惰性的

// 更好的选择是用 `for` 循环:
for x in 0..5 {
    println!("{x}");
}
filter
使用闭包筛选原迭代器每个元素,符合条件者进入新迭代器

该方法接收一个 FnMut(&Self::Item) -> bool 的闭包,然后方法会依次将原迭代器每个元素传入闭包。如果闭包返回 true 就将原迭代器对应元素放入新迭代器;如果闭包返回 false 就忽略它。最终返回新迭代器。

let a = [0i32, 1, 2];
// 筛选出所有正值
let mut iter = a.into_iter().filter(|x| x.is_positive());

assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

注意到,闭包接收的是引用类型参数。如果原先的迭代器就是对引用类型迭代的话,可能会出现某些令人困惑不解的双重引用。

此时对闭包的处理需要非常小心。

// 对下面这个集合直接调用 `iter`
// 产生的迭代器元素类型为 `&i32`
let s = &[0, 1, 2];
// 由于 `filter` 接收 `&Self::Item` 参数
// 而 `Self::Item` 为 `&i32` 类型
// 所以闭包的参数实际上是 `&&i32`

// `x` 需要解引用两次才能得到 `i32`
let mut iter = s.iter().filter(|x| **x > 1);

// 等价于

// `x` 需要解引用才能得到 `i32`,因为
// 闭包参数 `&x` 对应的类型是 `&&i32`
// 故 `x` 对应 `&i32`,只需解引用一次
let mut iter = s.iter().filter(|&x| *x > 1);

// 等价于

// 直接在参数中使用双重引用,这样就不需要解引用了
let mut iter = s.iter().filter(|&&x| x > 1);

assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), None);

迭代器的特性

惰性

迭代器(以及迭代器适配器)是惰性的。这意味着仅仅创建一个迭代器并不会做太多事情。在实际调用 next 之前,什么都不会真正发生。当仅为了其副作用而创建迭代器时,这有时会成为困惑的来源。例如,map 方法会在迭代的每个元素上调用一个闭包:

let v = vec![1, 2, 3, 4, 5];
v.iter().map(|x| println!("{x}"));

这段代码不会打印任何值,因为我们只创建了一个迭代器,而没有使用它。编译器会针对这种行为发出警告:

warning: unused result that must be used: iterators are lazy and
do nothing unless consumed

为了副作用而编写 map 的惯用方法是使用 for 循环或调用 for_each 方法:

let v = vec![1, 2, 3, 4, 5];

v.iter().for_each(|x| println!("{x}"));
// 或者
for x in &v {
    println!("{x}");
}

另一种常见的求值迭代器的方法是使用 collect 方法来生成一个新的集合。


无穷性

迭代器不一定是有限的。例如,一个无上限的区间就是一个无限迭代器:

let numbers = 0..;

通常使用 take 迭代器适配器将无限迭代器转换为有限迭代器:

let numbers = 0..;
let five_numbers = numbers.take(5);

for number in five_numbers {
    println!("{number}");
}

这将打印数字 0 到 4,每个数字占一行。

请记住,对于无限迭代器上的方法,即使那些在数学上可以在有限时间内确定结果的方法,也可能不会终止。具体来说,像 min 这样的方法,在一般情况下需要遍历迭代器中的每个元素,对于任何无限迭代器,很可能无法成功返回。

let ones = std::iter::repeat(1);
let least = ones.min().unwrap(); // 哦不!无限循环!
// `ones.min()` 导致无限循环,所以我们不会到达这一行!
println!("The smallest number one is {least}.");

参考资料