Rust 迭代器入门:Iterator trait、适配器与惰性求值
1. Iterator trait:一切从 next 开始
Iterator trait 的定义(标准库简化):
1 | pub trait Iterator { |
整个迭代器体系只靠一个方法驱动:next()。 每次调用推进迭代器一步,返回 Some(元素),耗尽后返回 None。
手动推进迭代器:
1 | let v = vec![1, 2, 3]; |
iter.next()需要&mut self——迭代器自身有状态,记录”走到了哪里”- 返回值是
Option<Self::Item>,这就是为什么迭代器天然处理”可能有、也可能没有下一个”的场景 - 你自己通常不直接写
next(),由for循环或 consuming adaptor 代劳
2. 创建迭代器:IntoIterator 与三种 iter 方法
for 循环不直接依赖 Iterator,而是依赖 IntoIterator trait:
1 | pub trait IntoIterator { |
for item in collection 的脱糖过程:
1 | // 你写的: |
三种迭代方式
Vec<T> 是 Rust 中最常见的可迭代类型,它提供了三种创建迭代器的方法:
| 方法 | 签名 | 返回类型 | 所有权 | 用途 |
|---|---|---|---|---|
iter() |
&self |
Iter<'_, T> → 元素为 &T |
借用,不转移 | 只读遍历 |
iter_mut() |
&mut self |
IterMut<'_, T> → 元素为 &mut T |
可变借用 | 遍历并修改 |
into_iter() |
self |
IntoIter<T> → 元素为 T |
消耗容器 | 转移所有权 |
1 | let v = vec![1, 2, 3]; |
常见容器的三种方式行为一致:Vec、HashMap、HashSet、String 等均遵循此模式。
for item in &v等价于v.iter(),for item in &mut v等价于v.iter_mut()into_iter()消耗容器后不能再使用——这是 Rust 所有权系统的保证
3. Iterator Adaptors:惰性转换器
Iterator adaptor 接收迭代器、返回新迭代器,不立即执行。 它们只是一个”转换计划”,直到某个 consuming adaptor 驱动时才真正跑起来。
核心语义:
1 | let v = vec![1, 2, 3, 4, 5]; |
必须有消费动作才会驱动计算:
1 | let result: Vec<_> = mapped.collect(); // collect 驱动 map 执行 |
常用 Iterator Adaptors
map — 逐元素变换:
1 | let v: Vec<i32> = (0..5).map(|x| x * 10).collect(); |
filter — 按条件筛选:
1 | let evens: Vec<_> = (0..10).filter(|x| *x % 2 == 0).collect(); |
take / skip — 取前 n 个 / 跳过前 n 个:
1 | let v: Vec<_> = (0..10).take(3).collect(); // [0, 1, 2] |
enumerate — 为每个元素附加索引:
1 | for (i, val) in ['a', 'b', 'c'].iter().enumerate() { |
chain — 拼接两个迭代器:
1 | let v: Vec<_> = (1..3).chain(10..13).collect(); |
zip — 将两个迭代器按位配对:
1 | let v: Vec<_> = (0..3).zip(['a', 'b', 'c']).collect(); |
- Iterator adaptor 可以无限链式串联,每个返回新的迭代器类型
- 闭包参数命名保持简洁(
x、v、i),链式调用中不引入无意义变量名
4. Consuming Adaptors:消费适配器
Consuming adaptor 是迭代器管道的终点——它调用 next() 循环推进,最终消耗迭代器,返回一个具体值或集合。一旦调用,之前的惰性链条全部执行。
4.1 收集与聚合
collect — 将迭代器收集到集合:
1 | let squares: Vec<i32> = (1..5).map(|x| x * x).collect(); |
collect 的目标类型靠类型标注推断——Vec<i32>、HashMap<_, _>、String 等,只要目标类型实现了 FromIterator。
sum / product — 求和 / 求积:
1 | let total: i32 = (1..=100).sum(); // 5050 |
count — 计数:
1 | let n = (0..100).filter(|x| x % 2 == 0).count(); |
4.2 查找与判定
find — 查找第一个满足条件的元素:
1 | let first = (0..100).find(|x| x % 7 == 0 && x > 50); |
any / all — 存在性 / 全称判定:
1 | let has_even = [1, 3, 5, 8].iter().any(|x| x % 2 == 0); // true |
any/all有短路语义:any遇到第一个 true 立即停止,all遇到第一个 false 立即停止
4.3 折叠与遍历
fold — 带初始值的累积计算:
1 | fn fold<B, F>(self, init: B, f: F) -> B |
1 | // 累加(等价于 sum) |
fold 的执行顺序(以 [a, b, c].fold(init, f) 为例):
1 | // 等价于: |
for_each — 对每个元素执行副作用操作:
1 | (0..5).for_each(|x| println!("{}", x)); |
for_each 与 for 循环功能等价,但在链式调用中更自然:
1 | (0..10) |
5. 链式调用:组合 Iterator Adaptors 与 Consuming Adaptors
迭代器的真正威力体现在链式组合——多个 adaptor 串联成一个管道,最后由 consuming adaptor 驱动执行:
1 | let v: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
整个链条在 .sum() 之前没有任何元素被处理——这是一个完整的惰性管道。
惰性求值的两大优势:
- 性能:不会为中间步骤分配临时
Vec,每个元素一路走完整个管道再到下一个元素 - 组合性:adaptor 可以自由组合、复用,行为由最后一步的 consuming adaptor 决定
对比手动循环写法:
1 | // 迭代器链式写法:声明式,意图清晰 |
两种写法等价,但迭代器链声明式表达”做什么”而非”怎么做”,意图更直观。
6. 小结
- Iterator trait 的核心是
next(),返回Option<Item>。所有其他方法基于next默认实现 - 创建迭代器通过
iter()(只读借用)、iter_mut()(可变借用)、into_iter()(消耗所有权),for循环脱糖为into_iter()+next()循环 - Iterator Adaptors(
map、filter、take、skip等)是惰性转换器——不调用就不执行 - Consuming Adaptors(
collect、sum、find、fold、for_each等)是管道终点——驱动整个链条执行 - 链式组合实现声明式数据处理,中间不产生临时分配
