🌀 一文看懂 Rust 迭代器
📚 目录导航
- 什么是迭代器?为什么 Rust 到处都在用它?
- Rust 迭代器的底层逻辑是什么?
- 适配器 vs 消费者:谁是主角?
- 常见适配器:加工数据的全能工厂
- 常见消费者:迭代器的“吃货家族”
- Rust 如何保证高性能?为什么懒执行这么厉害?
- ✅ 高阶技巧通俗讲透:by_ref、提前消费
- 🧠 小白也能写的自定义迭代器:从逻辑到实战
- 总结 + 常见场景整理
1️⃣ 什么是迭代器?为什么 Rust 到处都在用它?
一句话:迭代器就是一个可以“一个个吐出值”的东西。
再通俗点:它像一个工人,负责“从集合里拿出元素”:
let v = vec![1, 2, 3];
let mut it = v.iter();println!("{:?}", it.next()); // Some(1)
println!("{:?}", it.next()); // Some(2)
println!("{:?}", it.next()); // Some(3)
println!("{:?}", it.next()); // None
每次调用 .next()
,它就给你一个 Option<T>
类型的值:
- 有值:
Some(x)
- 没值了:
None
2️⃣ Rust 迭代器的底层逻辑是什么?
Rust 用 trait Iterator
抽象了所有“可以被一项一项访问”的数据结构:
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;
}
你只需要实现 next()
,它会一项一项地返回数据。每个适配器、消费者,都是围绕 next()
构建的!
3️⃣ 适配器 vs 消费者:谁才是主角?
对比 | 适配器(Adapter) | 消费者(Consumer) |
---|---|---|
功能 | 加工、变换、组合数据 | 真正“把数据用掉” |
是否懒执行 | ✅ 是 | ❌ 一调用就开始运行 |
是否返回迭代器 | ✅ 返回新的迭代器 | ❌ 返回值(Vec、i32、bool) |
示例 | .map() .filter() .take() | .collect() .sum() .find() |
类比思路 🧠:
适配器像工厂流水线,把原料(数据)加工好,
消费者像物流运输,把结果打包送到你手里。
4️⃣ 常见适配器:加工数据的全能工厂
名称 | 用法 | 示例 | 通俗场景 |
---|---|---|---|
map() | 加工每个元素 | .map(|x| x + 1) | 全部加 1、转大写等 |
filter() | 筛选元素 | .filter(|x| *x > 3) | 只要大于 3 的 |
take(n) | 只取前 n 个 | .take(2) | 分页、限流 |
skip(n) | 跳过前 n 个 | .skip(2) | 跳过表头等 |
enumerate() | 带索引 | .enumerate() | 打印编号 |
rev() | 倒序 | .rev() | 倒着打印 |
chain() | 拼接 | a.chain(b) | 合并多个集合 |
🌰 示例:
let result: Vec<_> = (1..=5).filter(|x| x % 2 == 1).map(|x| x * 10).collect();println!("{:?}", result); // [10, 30, 50]
5️⃣ 常见消费者:迭代器的“吃货家族”
名称 | 用法 | 示例 | 作用 |
---|---|---|---|
collect() | 收集为 Vec、HashMap 等 | .collect::<Vec<_>>() | 最常用 |
for_each() | 遍历做事 | .for_each(|x| println!("{}", x)) | 替代 for 循环 |
sum() | 求和 | .sum::<i32>() | 累加数字 |
product() | 求积 | .product::<i32>() | 全部相乘 |
count() | 个数 | .count() | 数据量统计 |
find() | 找一个满足条件的值 | .find(|x| *x == 3) | 搜索场景 |
any() | 有一个满足条件? | .any(|x| x > 5) | 判断存在性 |
all() | 全部满足条件? | .all(|x| x > 0) | 校验条件 |
6️⃣ Rust 如何保证高性能?为什么懒执行这么厉害?
Rust 的迭代器用的是“懒加载”思想:
💤 什么叫懒?
不会一开始就执行所有 .map()
和 .filter()
,而是只有你用 collect()
等消费器,才“触发”处理流程。
let iter = (1..=3).map(|x| {println!("处理 {}", x);x + 1
}); // 什么都不会打印let v: Vec<_> = iter.collect(); // 现在才开始打印
7️⃣ ✅ 高阶技巧通俗讲透
✅ by_ref()
是啥?
你拿一个可变引用传递给某个适配器,就能保留迭代器状态。
🌰 示例:我们只想用这个迭代器的一部分
let mut it = vec![1, 2, 3, 4, 5].iter();let a: Vec<_> = it.by_ref().take(2).collect(); // 拿前两个
let b: Vec<_> = it.collect(); // 剩下的还可以继续用println!("{:?} {:?}", a, b); // [1, 2] [3, 4, 5]
8️⃣ 🧠 小白也能写的自定义迭代器:从逻辑到实战
你只要记住两点:
✅ 一个迭代器必须:
- 有个结构体保存状态(比如当前数)
- 实现
Iterator
trait,提供next()
方法
🌰 例子:自己写一个“从 1 数到 5”的迭代器
struct CountToFive {count: usize,
}impl Iterator for CountToFive {type Item = usize;fn next(&mut self) -> Option<Self::Item> {if self.count < 5 {self.count += 1;Some(self.count)} else {None}}
}
✅ 用法:
fn main() {let counter = CountToFive { count: 0 };for n in counter {println!("{}", n); // 输出 1 到 5}
}
📌 模板套路(记住就能写):
struct MyIter { 状态字段 }impl Iterator for MyIter {type Item = 返回的类型;fn next(&mut self) -> Option<Self::Item> {// 判断是否结束 + 返回 Some(...) or None}
}
✅ 什么场景该自己写?
- 你要从 1 到 100,每次跳 7(非普通序列)
- 你要包装异步、分页、网络流
- 想把某个结构变成“可遍历”的对象
9️⃣ 总结:迭代器能做什么?为啥这么香?
优点 | 表现 |
---|---|
✅ 安全 | 没有手动索引越界的烦恼 |
✅ 高性能 | 零成本抽象,懒执行,自动内联 |
✅ 表达力强 | 写起来非常流畅,读起来像“数据管道” |
✅ 可组合 | map + filter + take 无限组合 |
✅ 可扩展 | 你可以实现自己的迭代器,复用逻辑 |
🚀 常见实战场景:
- 遍历 Vec、HashMap、文件行
- 过滤无效数据
- 求和 / 聚合统计
- 实现分页 / 分批处理
- 管道式业务数据处理(流式处理)