Rust 迭代器深入:惰性流水线与消费者
更新时间:2026-09-20。迭代器是 Rust「零成本抽象」的招牌:写出函数式风格的
map/filter链,编译后通常等价于手写循环。它依赖的闭包语法见 函数与闭包。
一、Iterator 与 IntoIterator
rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>; // 核心方法
// 其余方法都有默认实现
}- 实现了
Iterator就能用几十种适配器;for x in v实际要求v: IntoIterator,编译器插入对next()的调用。 - 三种迭代入口决定拿到什么:
| 入口 | 产出 | 遍历后集合 |
|---|---|---|
v.iter() | &T(只读) | 仍可用 |
v.iter_mut() | &mut T(可改) | 仍可用 |
v.into_iter() / for x in v | T(move) | 被消费 |
二、适配器是惰性的
map、filter、take、skip、zip、enumerate、flat_map 等只组装流水线,不执行任何元素处理:
rust
let v = vec![1, 2, 3, 4];
let chain = v.iter().map(|x| { x * 2 }).filter(|x| *x > 2);
// 到这里闭包一次都没运行!
let out: Vec<i32> = chain.collect(); // collect 才驱动整条链
assert_eq!(out, vec![4, 6, 8]);因此只写 v.iter().map(...) 而不消费,会得到 unused_must_use 警告且无任何效果。
三、消费者:真正驱动迭代
| 消费者 | 作用 |
|---|---|
collect::<C>() | 收成集合(Vec/String/HashMap),常需标注目标类型 |
| `fold(init, | acc, x |
sum() / product() | 求和/积 |
any(p) / all(p) | 存在/全部满足,短路 |
find / position / count | 查找、定位、计数 |
for_each(f) | 仅做副作用 |
rust
let total: i32 = (1..=10).filter(|x| x % 2 == 0).sum();
let folded = vec![1, 2, 3].iter().fold(0, |acc, x| acc + x);
let m = vec![1, 2, 3].into_iter().find(|x| *x == 2); // Option<i32>
let s: String = vec!["a", "b"].into_iter().collect(); // 标注成 String四、零成本从哪来
适配器是嵌套的泛型结构体(Map<Filter<Iter...>>),编译期单态化 + 内联,整条链常被优化成一个循环、无堆分配、无虚调用。这与 trait 泛型静态分发同源;需要动态分发时才用 &mut dyn Iterator,会付出 vtable 代价。
五、常见坑
- 惰性忘了消费:
iter().map(..)后面没collect/for,什么都不会发生。 - collect 目标不明:
let v = iter.collect()类型推断失败,写成let v: Vec<_> = ...。 - move 迭代后再用集合:
into_iter取得所有权,之后不能再访问原集合。 - 闭包借用冲突:迭代中同时改外部被借的集合违反借用规则,用
iter_mut或先收集结果。 - 迭代器一次性:
Iterator被next推进后不能从头复用,要重新iter();可复用语义用IntoIterator再次创建。
总结
Rust 迭代器的核心是 Iterator::next,for 循环经 IntoIterator 驱动,iter/iter_mut/into_iter 分别产出只读引用、可变引用和取得所有权的值;map/filter/take/zip 等适配器是惰性的,只把处理拼成泛型流水线、不跑闭包,必须由 collect/fold/sum/any 等消费者驱动才真正执行,collect 常需标注目标类型;借助单态化与内联,迭代器链通常被优化成无分配、无虚调用的单个循环,是和 trait 静态分发同源的零成本抽象,使用时注意惰性必须消费、into_iter 会 move 走集合、迭代器只能推进一次。