迭代器
Rust 的迭代器(iterators)是处理序列数据的强大工具,允许你以懒惰(lazy)方式遍历集合,而不立即计算所有元素。这提高了效率,尤其在链式操作中。迭代器实现了 Iterator trait,提供 next() 方法返回 Option<Item>。Rust 标准库中的许多类型如 Vec、HashMap、Range 等都支持迭代器。迭代器是零成本抽象,编译时优化。
一、 核心概念
1. 迭代器的本质:延迟计算(Lazy Evaluation)
在 Rust 中,创建一个迭代器并不会立刻执行任何计算。它更像是一个“处方”或“计划书”。只有当你真正开始向迭代器“要东西”时,它才会开始工作。
为什么我的代码没运行?
很多初学者写了 map 却发现没有任何输出,这就是因为没有触发“消费”。这就是 Lazy:先建管道,后执行。
fn main() {
let v = vec![1, 2, 3];
// 这一行代码几乎不消耗时间,也不会打印任何东西
// 它只是创建了一个“计划”:把每个元素加 1
let v_iter = v.iter().map(|x| {
println!("正在计算: {}", x);
x + 1
});
println!("迭代器已创建,准备开始...");
// 只有到了这一步,或者使用 for 循环,计算才会真正发生
let result: Vec<_> = v_iter.collect();
println!("最终结果: {:?}", result);
}
2. 迭代器与 for 循环的关系
核心逻辑:Rust 的 for 循环其实是语法糖。当你写 for x in items 时,编译器在底层会自动将其转换为迭代器调用。
编译器做了什么?
当你写下这段代码:
#![allow(unused)]
fn main() {
for x in vec![1, 2, 3] {
println!("{}", x);
}
}
编译器实际上将其“展开”为类似这样的逻辑:
#![allow(unused)]
fn main() {
let mut iter = vec![1, 2, 3].into_iter(); // 转换为迭代器
while let Some(x) = iter.next() { // 不断调用 next()
println!("{}", x);
}
}
- 注意:迭代器内部维护了一个状态(通常是一个指针或索引),每次调用
next(),状态就会向后移动。
3. 消费端(Consumers)与适配器(Adapters)
迭代器的操作可以分为两类,理解它们的区别是掌握数据流的关键。
| 类别 | 作用 | 特点 | 例子 |
|---|---|---|---|
| 适配器 (Adapters) | 将一个迭代器转变为另一个迭代器 | 惰性。不触发计算,只定义变换逻辑。 | map, filter, zip, take |
| 消费端 (Consumers) | 启动迭代过程,产生最终结果 | 主动。会调用 next(),触发整个链条的运行。 | collect, sum, fold, count |
组合的威力
你可以通过适配器构建一条复杂的“流水线”,最后用一个消费端收尾。
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
// 流水线设计:
let sum: i32 = numbers.iter()
.filter(|&&x| x % 2 == 0) // 适配器:只要偶数
.map(|&x| x * x) // 适配器:平方
.take(2) // 适配器:只要前两个
.sum(); // 消费端:触发所有计算并求和
println!("前两个偶数的平方和: {}", sum); // (2*2) + (4*4) = 20
}
二、三大核心 Trait
Iterator Trait:核心接口,理解next()方法与Item关联类型IntoIterator Trait:如何将集合(如Vec, HashMap)转换为迭代器FromIterator Trait:collect()方法背后的原理(如何将迭代器转回集合)
在 Rust 中,迭代器不是某种特殊的语言构造,而是通过三个核心 Trait 构建的体系。理解了这三个 Trait,你就理解了数据如何在“集合”与“流水线”之间流转。 这一部分你只要抓住一条主线就够了:
for 负责把东西变成迭代器(IntoIterator) → 迭代器用 next() 吐元素(Iterator) → collect() 把元素“装回去”(FromIterator)
可以把它想成一条流水线:
集合/自定义类型 ──(IntoIterator)──> 迭代器 ──(Iterator::next)──> 元素序列 ──(FromIterator/collect)──> 新集合
1. Iterator Trait:迭代器的基石
这是最核心的 Trait。只要一个类型实现了它,它就是一个迭代器。
最核心的两个东西:Item + next()
Iterator 的本质就是一个状态机:每次调用 next(),迭代器推进内部状态并尝试产出一个元素。
next() 的签名必须是
&mut self:因为每次迭代都会改变“我迭到哪了”的内部状态。
典型形态(要记住结构):
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
type Item:关联类型,定义了迭代器每次产出什么。fn next(&mut self) -> Option<Self::Item>:这是唯一需要手动实现的方法。
手动实现一个“步进器”
通过手动实现,你会发现迭代器只是一个记录了当前状态的结构体。
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { count: 0, max }
}
}
// 手动实现 Iterator
impl Iterator for Counter {
type Item = u32; // 告诉编译器,我们产出的是 u32
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None // 返回 None 代表迭代结束
}
}
}
fn main() {
let mut counter = Counter::new(3);
// 我们可以手动调用 next
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), None);
}
手动调用 next():最直观理解迭代器
fn main() {
let v = vec![10, 20, 30];
// Vec<T> 的 into_iter() 生成一个“拿走所有权”的迭代器
let mut it = v.into_iter();
println!("{:?}", it.next()); // Some(10)
println!("{:?}", it.next()); // Some(20)
println!("{:?}", it.next()); // Some(30)
println!("{:?}", it.next()); // None
println!("{:?}", it.next()); // 依然 None(很多迭代器是 fused 的,但不是 trait 强制)
}
你看到的就是:迭代器每次吐一个元素,吐完就 None。
Iterator 的“很多方法”从哪来的?
map/filter/take/fold/sum/… 这些几乎都是 Iterator trait 上的默认方法,基于 next() 组合出来的。你只要实现 next(),Rust 标准库就送你一整套“函数式管道工具”。
fn main() {
let sum: i32 = (1..=10)
.map(|x| x * 2) // 适配器:生成新迭代器
.filter(|x| x % 3 == 0)
.sum(); // 消耗器:触发执行
println!("{sum}");
}
2. IntoIterator Trait:集合如何变成迭代器(for 循环背后)
核心逻辑:这个 Trait 定义了如何将一个非迭代器类型(如 Vec)转换为迭代器。这也是 for 循环能够工作的根本原因。
for 循环到底做了什么?
for x in something 做的第一件事是:
调用
something.into_iter()(准确地说是IntoIterator::into_iter(something))
然后不断 next()。
等价理解(伪展开):
let mut iter = something.into_iter();
while let Some(x) = iter.next() {
// ...
}
## `IntoIterator`的定义
```rust,ignore
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
这里的关键是:
IntoIterator负责“怎么生成迭代器”- 生成的迭代器必须实现
Iterator Item决定了你在for x in ...里拿到的x是什么类型
同一个容器有 3 种常见 IntoIterator 实现
以 Vec<T> 为例:
Vec<T>(按值)→into_iter()产出T(消耗原集合)&Vec<T>(共享借用)→into_iter()产出&T&mut Vec<T>(可变借用)→into_iter()产出&mut T
fn main() {
let v = vec![String::from("a"), String::from("b")];
// 1) 借用遍历:拿到 &String
for s in &v {
println!("borrowed: {s}");
}
println!("still have v: {:?}", v);
// 2) 所有权遍历:拿到 String(v 被 move 走)
for s in v {
println!("owned: {s}");
}
// println!("{:?}", v); // ❌ v 已经被消耗
}
自定义类型实现 IntoIterator:让它能直接 for .. in ..
比如自定义一个范围类型:
struct MyRange {
start: i32,
end: i32,
}
struct MyRangeIter {
cur: i32,
end: i32,
}
impl Iterator for MyRangeIter {
type Item = i32;
fn next(&mut self) -> Option<i32> {
if self.cur >= self.end {
None
} else {
let v = self.cur;
self.cur += 1;
Some(v)
}
}
}
impl IntoIterator for MyRange {
type Item = i32;
type IntoIter = MyRangeIter;
fn into_iter(self) -> MyRangeIter {
MyRangeIter { cur: self.start, end: self.end }
}
}
fn main() {
for x in MyRange { start: 0, end: 3 } {
println!("{x}");
}
}
3. FromIterator Trait:流水线的终点 (collect)
核心逻辑:这是 collect() 方法背后的英雄。它定义了如何将迭代器中的元素“收集”回一个新的集合。
collect() 不是魔法,它靠 FromIterator
你可以把 collect() 理解成:
“请把这个迭代器的元素,按目标类型的规则装起来”
装的规则来自:
trait FromIterator<A> {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}
能 collect 成什么类型,取决于那个类型实现没实现 FromIterator。
collect() 为什么经常需要你标注类型?
因为光看迭代器元素,编译器不知道你要装进 Vec 还是 HashSet 还是别的。
两种常见写法:
fn main() {
let v = vec![1, 2, 3];
// 写法 1:变量类型标注
let a: Vec<i32> = v.iter().map(|x| x * 10).collect();
println!("{a:?}");
// 写法 2:turbofish
// let a = v.iter().map(|x| x * 10).collect::<Vec<i32>>();
// println!("{a:?}");
}
常见 collect 目标:Vec / HashMap / String
collect 到 Vec:
fn main() {
let out: Vec<i32> = (0..5).map(|x| x * 2).collect();
println!("{out:?}");
}
collect 到 HashMap:
use std::collections::HashMap;
fn main() {
let pairs = vec![("a", 1), ("b", 2)];
let map: HashMap<&str, i32> = pairs.into_iter().collect();
println!("{map:?}");
}
collect 到 String(从 char 序列):
fn main() {
let s: String = ['R', 'u', 's', 't'].into_iter().collect();
println!("{s}");
}
collect::<Result<Vec<_>, _>>():FromIterator 的“高级爽点”
Result(以及 Option)也实现了非常强大的 FromIterator:
- 只要所有元素都是
Ok(x),就collect成Ok(Vec<x>) - 只要遇到一个
Err(e),就立刻返回Err(e)(短路)
fn main() {
let inputs = vec!["10", "20", "nope", "40"];
let parsed: Result<Vec<i32>, _> = inputs
.into_iter()
.map(|s| s.parse::<i32>())
.collect();
println!("{parsed:?}"); // Err(...)
}
这在解析、校验、批处理里非常常用:把错误处理“自动织入” collect。
自定义 FromIterator:让别人能 collect::<你的类型>()
我们实现一个 EvenVec:只收集偶数(演示“自定义收集规则”)。
#[derive(Debug)]
struct EvenVec(Vec<i32>);
impl FromIterator<i32> for EvenVec {
fn from_iter<T: IntoIterator<Item = i32>>(iter: T) -> Self {
let mut out = Vec::new();
for x in iter {
if x % 2 == 0 {
out.push(x);
}
}
EvenVec(out)
}
}
fn main() {
let evens: EvenVec = (0..10).collect();
println!("{evens:?}"); // EvenVec([0,2,4,6,8])
}
三、迭代器的三种模式
核心逻辑:在 Rust 中,迭代器不仅决定如何处理数据,还决定了你是拥有这些数据,还是仅仅借用这些数据。这直接关系到借用检查器(Borrow Checker)是否会报错。
几乎所有的集合(如 Vec, HashMap, BTreeMap)都提供了这三种方法。
这三者的核心差异只有一件事:**迭代时“元素的所有权/借用”**到底归谁。
iter():共享借用(read-only),产出&Titer_mut():可变借用(read-write),产出&mut Tinto_iter():拿走所有权(move),产出 T(通常会消耗原集合)
iter 借只读,iter_mut 借可写,into_iter 拿走不还。
1. iter():不可变借用模式(&T)
- 产生类型:产生元素的不可变引用
&T。 - 使用场景:你只想读取集合中的数据,而不改变数据,也不想销毁集合。
- 知识点:不移动集合元素,只是“借来看看”。迭代结果类型:&T。原集合仍可继续使用(只要借用期结束)。
代码演示:读取但不修改
fn main() {
let v = vec![1, 2, 3];
// iter() 产生 &i32
let mut v_iter = v.iter();
assert_eq!(v_iter.next(), Some(&1));
assert_eq!(v_iter.next(), Some(&2));
assert_eq!(v_iter.next(), Some(&3));
// 重点:集合 v 在迭代后依然可以继续使用
println!("v 依然存在: {:?}", v);
}
代码演示:map 时注意解引用
fn main() {
let v = vec![1, 2, 3];
// iter() 产生 &i32,因此 map 的参数是 &i32
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
// 这里 x 是 &i32,但 * 运算符对 &i32 会自动解引用(Deref coercion / Copy)
println!("{:?}", doubled);
}
2. iter_mut():可变借用模式(&mut T)
- 产生类型:产生元素的可变引用
&mut T。 - 使用场景:你需要原地修改集合中的每一个元素。
- 知识点:允许修改元素内容,但仍不拿走所有权。迭代结果类型:
&mut T。迭代期间,集合被“独占可变借用”,不能同时被其他借用使用。
代码演示:原地修改元素
fn main() {
let mut v = vec![1, 2, 3];
// iter_mut() 产生 &mut i32
// 注意:必须要求 v 本身是 mut 的
for x in v.iter_mut() {
*x *= 2; // 通过解引用修改原始值
}
println!("v 已被原地修改: {:?}", v); // [2, 4, 6]
}
代码演示:配合 enumerate 做位置相关修改
fn main() {
let mut v = vec![5, 5, 5, 5];
for (i, x) in v.iter_mut().enumerate() {
*x += i as i32;
}
println!("{:?}", v); // [5, 6, 7, 8]
}
3. into_iter():所有权模式(T)
- 产生类型:产生元素本身
T。 - 使用场景:你需要获取元素的所有权(例如移动到另一个线程,或者转换类型),并且不再需要原始集合。
- 知识点:
- 消耗原集合(通常 move),把元素一个个“搬走”。
- 迭代结果类型:T
- 原集合之后一般不能再用(已经 moved)。
重要:into_iter() 的具体行为和返回类型,取决于你对谁调用:
- 对
Vec<T>调用:通常产出 T(拿走元素) - 对
&Vec<T>调用:相当于 iter(),产出&T - 对
&mut Vec<T>调用:相当于 iter_mut(),产出&mut T
代码演示:消费集合,拿到元素所有权
fn main() {
let v = vec![String::from("a"), String::from("b")];
// into_iter() 产生 String(所有权)
let upper: Vec<String> = v.into_iter()
.map(|s| s.to_uppercase())
.collect();
println!("{:?}", upper);
// println!("{:?}", v); // ❌ v 已被 move(被 into_iter 消耗)
}
代码演示:对引用调用 into_iter(类型不同)
fn main() {
let v = vec![1, 2, 3];
let a: Vec<i32> = (&v).into_iter().copied().collect(); // &Vec -> &i32
let b: Vec<i32> = v.into_iter().collect(); // Vec -> i32 (move)
println!("{:?}", a);
println!("{:?}", b);
}
三种模式深度对比表
| 方法 | 产生的 Item | 背后 Trait 实现 | 集合之后是否可用? | 核心性质 |
|---|---|---|---|---|
iter() | &T | impl IntoIterator for &Vec<T> | 是 | 只读观察 |
iter_mut() | &mut T | impl IntoIterator for &mut Vec<T> | 是 | 原地手术 |
into_iter() | T | impl IntoIterator for Vec<T> | 否 | 彻底消耗 |
fn main() {
let mut v = vec![1, 2, 3];
let it1 = v.iter(); // Iterator<Item = &i32>
let it2 = v.iter_mut(); // Iterator<Item = &mut i32>
let it3 = v.into_iter(); // Iterator<Item = i32> (v 被 move)
}
for 循环到底用的是谁?
for x in something {} 本质上会调用 IntoIterator
for x in v→v.into_iter()(可能消耗v)for x in &v→(&v).into_iter()(等价iter)for x in &mut v→(&mut v).into_iter()(等价iter_mut)
代码演示:三种 for 写法
fn main() {
let mut v = vec![10, 20, 30];
for x in &v {
// x: &i32
println!("read {x}");
}
for x in &mut v {
// x: &mut i32
*x += 1;
}
for x in v {
// x: i32,v 被消费
println!("owned {x}");
}
// println!("{:?}", v); // ❌ v 已被消费
}
选型建议:什么时候用哪个?
- 只读遍历:用
iter()/for x in &v - 原地改元素:用
iter_mut()/for x in &mut v - 需要拿到元素所有权(如 String 拼接/转移/线程传递):用
into_iter()/for x in v - 想保留原集合,但又想得到“拥有的值”:
iter().cloned()(针对Clone)iter().copied()(针对Copy)
fn main() {
let v = vec![1, 2, 3];
let owned1: Vec<i32> = v.iter().copied().collect(); // 不消耗 v
let owned2: Vec<i32> = v.clone().into_iter().collect(); // 通过 clone 再消费
println!("{:?}", owned1);
println!("{:?}", owned2);
println!("{:?}", v);
}
四、常用迭代器适配器
核心逻辑:适配器(Adapters)是迭代器的“加工车间”。它们接收一个迭代器,经过某种变换,输出一个新的迭代器。最重要的一点是:它们是惰性的(Lazy),除非最后的“消费端”(如 collect)发出指令,否则这些加工车间根本不会开工。
1. 基础转换:map 与 filter
这是最常用的组合,类似于 SQL 中的 SELECT 和 WHERE。
map:对每个元素执行转换。filter:根据布尔值决定是否保留元素。
map:逐元素变换
- 用途:把 Item 变成另一个东西。
- 签名直觉:
Iterator<Item=A> -> Iterator<Item=B>
示例:&i32 / i32 的差异(最常见坑)
fn main() {
let v = vec![1, 2, 3];
// v.iter() 产出 &i32,所以闭包参数是 &i32
let a: Vec<i32> = v.iter().map(|&x| x * 10).collect();// 写法 A:参数里解构, 把 &i32 解引用一层,绑定到 x: i32
//写法 B:闭包体里解引用:
// let b: Vec<i32> = v.iter().map(|x| *x * 10).collect();
// 或者:map(|x| x * 10) 也能过(很多运算会自动解引用),但建议显式 |&x| 养成习惯
// v.into_iter() 产出 i32,闭包参数是 i32
let b: Vec<i32> = v.into_iter().map(|x| x * 10).collect();
println!("{a:?} {b:?}");
}
filter:筛选(0/1 对 1)
- 用途:按条件保留元素。
- 关键点:闭包拿到的是 引用的引用 时要解两次(比如 v.iter())
示例:过滤偶数(注意 |&&x|)
fn main() {
let v = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = v.iter()
.filter(|&&x| x % 2 == 0) // 这里 x: &&i32
.copied()
.collect();
println!("{evens:?}"); // [2, 4]
}
常用替代:filter_map(过滤 + 映射,一步到位)
这在实际项目里非常高频(解析、容错、跳过非法数据)
fn main() {
let v = vec!["1", "oops", "3", "-7"];
let nums: Vec<i32> = v.into_iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("{nums:?}"); // [1, 3, -7]
}
2. 结构操作:zip 与 chain
zip:将两个迭代器“拉”在一起,像拉链一样产生一对对的元组(a, b)。如果长度不等,以短的为准。chain:将两个迭代器“接”在一起,一个完了接另一个。
zip:并行配对(短的结束就结束)
- 用途:将两个迭代器“拉”在一起,并行产出元组
(a, b)。 - 输出类型:
Iterator<Item=(A, B)>
fn main() {
let names = vec!["alice", "bob", "cindy"];
let ages = vec![20, 30];
// 只会产出 2 组:因为 ages 更短
let out: Vec<_> = names.iter()
.zip(ages.iter())
.map(|(&n, &a)| (n, a))
.collect();
println!("{out:?}"); // [("alice",20), ("bob",30)]
}
chain:拼接两个迭代器(类型要一致)
- 用途:将两个迭代器“接”在一起,一个完了接另一个。
- 输出类型:
Iterator<Item=A>(或Iterator<Item=B>,取决于输入)
fn main() {
let a = vec![1, 2];
let b = vec![3, 4];
let out: Vec<i32> = a.into_iter().chain(b.into_iter()).collect();
println!("{out:?}"); // [1,2,3,4]
}
3. 处理嵌套:flatten 与 flat_map
实践场景:当你有一个 Vec<Vec<T>> 或者迭代器产生的元素本身又是 Option 或 Result 时。
flatten:把嵌套结构“拍平”一层。flat_map:先map再flatten。
flatten:拍平一层(元素本身是 iterable)
- 用途:把
Vec<Vec<T>>拍平成Vec<T>。 - 输出类型:
Iterator<Item=T>
典型:Vec<Vec<T>>、Vec<Option<T>>、Iterator<Item=Result<T,E>>(配合技巧)
fn main() {
let v = vec![vec![1, 2], vec![3], vec![4, 5]];
let out: Vec<i32> = v.into_iter().flatten().collect();
println!("{out:?}"); // [1,2,3,4,5]
}
经典技巧:Option 的 flatten = “把 Some 留下,把 None 丢掉”
fn main() {
let v = vec![Some(1), None, Some(3)];
let out: Vec<i32> = v.into_iter().flatten().collect();
println!("{out:?}"); // [1,3]
}
flat_map:map + flatten(常用于拆分/展开)
- 用途:先
map再flatten,常用于拆分/展开。 - 输出类型:
Iterator<Item=T>
示例:拆分字符串(按空格)
fn main() {
let lines = vec!["hello world", "rust iter"];
let words: Vec<&str> = lines.into_iter()
.flat_map(|line| line.split_whitespace())
.collect();
println!("{words:?}"); // ["hello","world","rust","iter"]
}
4. 引用处理:cloned 与 copied
当你在使用 iter()(产生 &T)但后续操作需要 T 时,这两个适配器非常有用。
-
cloned():调用clone()产生拥有所有权的值(适用于String等)。 -
copied():调用按位拷贝(适用于i32等实现了Copy的类型)。 -
copied():&T -> T,要求 T: Copy(如 i32、bool、char)
-
cloned():&T -> T,要求 T: Clone(如 String、Vec、Arc 等)
fn main() {
let a = vec![1, 2, 3];
let x: Vec<i32> = a.iter().copied().collect();
let b = vec!["hi".to_string(), "rust".to_string()];
let y: Vec<String> = b.iter().cloned().collect();
println!("{x:?}");
println!("{y:?}");
}
5. 截取与跳过:take, skip, take_while, skip_while
take(n):只取前n个元素。skip(n):跳过前n个元素。take_while(pred):取元素直到pred为false。skip_while(pred):跳过元素直到pred为false。
take(n) / skip(n):按数量切
fn main() {
let v = vec![1, 2, 3, 4, 5];
let a: Vec<i32> = v.iter().take(3).copied().collect(); // [1,2,3]
let b: Vec<i32> = v.iter().skip(3).copied().collect(); // [4,5]
println!("{a:?} {b:?}");
}
take_while / skip_while:按条件切(遇到不满足就停止/开始)
fn main() {
let v = vec![1, 2, 3, 4, 3, 2];
let a: Vec<i32> = v.iter().take_while(|&&x| x < 4).copied().collect();
let b: Vec<i32> = v.iter().skip_while(|&&x| x < 4).copied().collect();
println!("{a:?}"); // [1,2,3]
println!("{b:?}"); // [4,3,2] 注意:从第一个不满足开始,后面不再检查条件
}
skip_while 不是“过滤”,它只在开头连续跳过;一旦开始产出,后续不会再跳。
6. 辅助工具:enumerate 与 inspect
enumerate:在迭代时顺便产出索引(index, value)。inspect:不修改元素,只是查看(常用于调试,看看流水线中间的状态)。
enumerate:给元素附上索引
- 用途:做“位置相关”的逻辑(比如给每个元素编号)。
- 输出类型:
(usize, Item)
fn main() {
let v = vec!["a", "b", "c"];
let pairs: Vec<(usize, &str)> = v.iter()
.enumerate()
.map(|(i, &s)| (i, s))
.collect();
println!("{pairs:?}"); // [(0,"a"), (1,"b"), (2,"c")]
}
调试辅助:inspect
- 用途:在链条中间打印/埋点,不改变元素。
- 注意:仍然 lazy,只有终结器触发才会打印。
fn main() {
let out: Vec<i32> = (1..=5)
.inspect(|x| println!("before map: {x}"))
.map(|x| x * 10)
.inspect(|x| println!("after map: {x}"))
.filter(|x| x >= &30)
.collect();
println!("{out:?}");
}
适配器选型
下面是提到的这些**迭代器适配器(中间操作)**的汇总表
| 分类 | 适配器 | 作用 |
|---|---|---|
| 基础转换 | map | 对每个元素做映射变换(1→1),生成新迭代器 |
| 基础转换 | filter | 按条件过滤元素(保留满足条件的) |
| 基础转换 | enumerate | 给每个元素附带索引 (usize, item) |
| 结构操作 | zip | 把两个迭代器按位置配对成 (a, b),以较短者结束 |
| 结构操作 | chain | 把两个迭代器首尾拼接成一个连续迭代器 |
| 结构操作 | flatten | 拍平一层:把“元素本身可迭代”的迭代器展开一层 |
| 结构操作 | flat_map | map 后再 flatten:每个元素映射为迭代器并展开 |
| 截取/跳过 | take | 只取前 n 个元素 |
| 截取/跳过 | skip | 跳过前 n 个元素 |
| 截取/跳过 | take_while | 从头开始取,直到条件首次不满足就停止 |
| 截取/跳过 | skip_while | 从头开始跳过,直到条件首次不满足就开始产出(之后不再检查条件) |
| 调试辅助 | inspect | 在迭代链中插入观察/打印,不改变元素(仍是 lazy) |
| 引用处理 | cloned | 把 &T 变成 T(要求 T: Clone) |
| 引用处理 | copied | 把 &T 变成 T(要求 T: Copy) |
五、 常用消耗器(终结操作)
核心逻辑:消耗器(Consumers)是流水线的“出口”。如果没有消耗器,前面的适配器(如 map, filter)永远不会执行。消耗器会通过循环不断调用迭代器的 next() 方法,直到返回 None 为止。一旦调用了消耗器,该迭代器就被消耗掉了,不能再次使用。
终极集合器:collect
这是最常见的消耗器,我们在前几节已经多次用到。它将迭代器中的元素收集到某种集合中(如 Vec, HashMap, String 等)。
- 特点:高度泛型,通常需要类型暗示。
- 技巧:它可以将
Option<T>的迭代器收集成Option<Vec<T>>。
关键点:类型推断
编译器不知道你要收集成什么就会报错,所以常见写法:
fn main() {
let v = vec![1, 2, 3];
let a = v.iter().map(|&x| x * 10).collect::<Vec<i32>>();
let b: Vec<i32> = v.iter().map(|&x| x * 10).collect();
println!("{a:?} {b:?}");
}
收集成 HashMap
fn main() {
let v = vec![1, 2, 3];
// collect 触发了迭代
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
// 进阶:处理 Option。如果其中有一个 None,最终结果就是 None
let maybe_numbers = vec![Some(1), Some(2), None];
let result: Option<Vec<i32>> = maybe_numbers.into_iter().collect();
println!("{:?}", result); // None
}
基础聚合:sum, product, count
这些操作非常直观,用于数值计算或统计数量。
count() 返回元素个数(usize)
fn main() {
let c = (1..=10).filter(|x| x % 2 == 0).count();
println!("{c}"); // 5
}
sum() / product() : 返回元素总和 / 乘积
fn main() {
let v = vec![1, 2, 3, 4];
let s: i32 = v.iter().sum();
let p: i32 = v.iter().product();
println!("{s} {p}"); // 10 24
}
查找与匹配: find, position, any, all, nth
核心优势:这些方法具有**短路(Short-circuiting)**特性。例如,any 只要找到一个满足条件的元素,就会立即停止迭代,不再处理后续数据。这对于性能优化至关重要。
any:只要有一个符合条件就返回true。all:必须全部符合条件才返回true。find:返回第一个符合条件的元素的引用(包裹在Option中)。position:返回第一个符合条件的元素的索引(Option<usize>)。
find 找到第一个满足条件的元素(返回 Option<Item>)
fn main() {
let v = vec![10, 20, 30, 40];
let f = v.iter().find(|&&x| x >= 25);
println!("{f:?}"); // Some(&30)
}
position 找到第一个满足条件的索引(返回 Option<usize>)
fn main() {
let v = vec![10, 20, 30, 40];
let p = v.iter().position(|&x| x == 30);
println!("{p:?}"); // Some(2)
}
any / all
any:只要有一个符合条件就返回true。all:必须全部符合条件才返回true。
fn main() {
let v = vec![2, 4, 6, 7];
println!("{}", v.iter().any(|&x| x % 2 == 1)); // true
println!("{}", v.iter().all(|&x| x % 2 == 0)); // false
}
nth(k): 取第 k 个元素,但会消耗掉前 k 个(不是随机访问)
fn main() {
let mut it = (10..=20);
println!("{:?}", it.nth(3)); // Some(13) 消耗 10,11,12,13
println!("{:?}", it.next()); // Some(14) 迭代器状态已经推进
}
折叠与归约:fold, reduce, scan
这三个是“最像算法”的消耗器/半消耗器。
fold(init, f):你提供初始值
- 总是返回一个值(不会是 Option)
- 适合:累计、构建字符串、构建 map、统计等
fn main() {
let v = vec![1, 2, 3];
let r = v.iter().fold(100, |acc, &x| acc + x);
println!("{r}"); // 106
}
典型:fold 拼字符串
fn main() {
let words = vec!["hello", "rust", "iter"];
let s = words.iter().fold(String::new(), |mut acc, &w| {
if !acc.is_empty() { acc.push(' '); }
acc.push_str(w);
acc
});
println!("{s}");
}
reduce(f):用第一个元素当初始值(返回 Option)
- 空迭代器会返回 None
- 适合:最大值、求和等“有自然单位元但不想写 init”的场景
fn main() {
let v = vec![1, 2, 3];
let r = v.into_iter().reduce(|acc, x| acc + x);
println!("{r:?}"); // Some(6)
}
scan(state, f):它是适配器(但非常像 fold)
- scan 会产生一个新迭代器,把“中间状态”也作为输出
- 适合:前缀和、状态机、解析流
fn main() {
let v = vec![1, 2, 3, 4];
// 前缀和:1,3,6,10
let prefix: Vec<i32> = v.into_iter()
.scan(0, |state, x| {
*state += x;
Some(*state)
})
.collect();
println!("{prefix:?}");
}
极值处理:max,min,max_by,min_by
返回迭代器中的最大值或最小值。注意它们返回的是 Option,因为迭代器可能没有元素。
- 对于复杂结构,可以使用
max_by或max_by_key。
max/min
fn main() {
let v = vec![3, 10, 7];
println!("{:?}", v.iter().max()); // Some(&10)
println!("{:?}", v.iter().min()); // Some(&3)
}
自定义比较:max_by / min_by
比如按字符串长度找最长:
fn main() {
let v = vec!["aa", "bbbb", "ccc"];
let best = v.iter().max_by(|a, b| a.len().cmp(&b.len()));
println!("{best:?}"); // Some("bbbb")
}
本节“选择器”:我到底该用谁?
| 类别 | 名字 | 作用 | 返回/特点 |
|---|---|---|---|
| 集合转换 | collect | 把迭代器收集成集合(Vec/HashMap/HashSet/...) | 返回目标集合类型;常需标注 collect::<Vec<_>>() |
| 基础聚合 | count | 统计元素个数 | usize |
| 基础聚合 | sum | 求和 | 返回数值类型(需要可推断) |
| 基础聚合 | product | 连乘 | 返回数值类型(需要可推断) |
| 折叠/归约 | fold | 自定义累计(你提供初始值) | 返回累计结果(不会是 Option) |
| 折叠/归约 | reduce | 用第一个元素做初始值的归约 | Option<Item>,空迭代器为 None |
| 折叠/归约 | scan | 带状态地产生“中间结果序列”(前缀和/状态机) | 注意:它是适配器,返回新迭代器;通常再 collect() |
| 查找/匹配 | find | 找到第一个满足条件的元素 | Option<Item>(借用迭代器通常是 Option<&T>) |
| 查找/匹配 | position | 找到第一个满足条件的索引 | Option<usize> |
| 查找/匹配 | any | 是否存在任意元素满足条件(短路) | bool |
| 查找/匹配 | all | 是否所有元素都满足条件(短路) | bool |
| 查找/匹配 | nth | 取第 n 个元素(从 0 开始) | Option<Item>;会消耗掉前 n 个 |
| 极值处理 | max | 取最大元素 | Option<Item>(借用迭代器常为 Option<&T>) |
| 极值处理 | min | 取最小元素 | Option<Item> |
| 极值处理 | max_by | 自定义比较规则取最大 | Option<Item>,比较由闭包决定 |
| 极值处理 | min_by | 自定义比较规则取最小 | Option<Item>,比较由闭包决定 |
六、自定义迭代器
核心逻辑:在 Rust 中,要让一个结构体变成迭代器,你不需要继承任何复杂的类,只需要实现 Iterator 这个 Trait。你唯一需要做的,就是告诉编译器如何产出下一个元素(next 方法)以及产出什么(Item 类型)。
1. 实现 Iterator Trait 的两要素
要实现自定义迭代器,你的结构体必须具备两点:
- 状态维护:结构体里需要有字段记录当前迭代到了哪里。
next方法:每次调用时更新状态,并返回Some(value)或None。
斐波那契数列迭代器
斐波那契数列是一个完美的自定义迭代器例子,因为它具有清晰的内部状态转移。
struct Fibonacci {
curr: u32,
next: u32,
}
impl Fibonacci {
fn new() -> Fibonacci {
Fibonacci { curr: 0, next: 1 }
}
}
// 为 Fibonacci 实现 Iterator
impl Iterator for Fibonacci {
type Item = u32; // 产出 u32 类型
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
// 计算下一项并更新状态
self.curr = self.next;
self.next = current + self.next;
// 斐波那契数列通常是无限的,但在 Rust 中我们需要设置一个边界
// 这里假设超过 1000 就停止
if current > 1000 {
None
} else {
Some(current)
}
}
}
fn main() {
let fib = Fibonacci::new();
// 一旦实现了 Iterator,你就可以使用 map, filter 等所有适配器!
for num in fib.take(10) {
println!("{}", num);
}
}
2. 为集合实现 IntoIterator
如果你创建了一个自定义集合(比如 MyList),你可能希望直接写 for x in my_list。这时你需要实现 IntoIterator。
核心逻辑:IntoIterator 的作用是定义“如何从你的集合产生一个迭代器”。
struct MyCollection {
items: Vec<i32>,
}
impl IntoIterator for MyCollection {
type Item = i32;
type IntoIter = std::vec::IntoIter<i32>; // 直接复用 Vec 的迭代器类型
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
fn main() {
let coll = MyCollection { items: vec![1, 2, 3] };
// 现在可以直接在 for 循环中使用
for x in coll {
println!("{}", x);
}
}
3. 自定义迭代器的优势:节省内存
自定义迭代器最大的魅力在于:它是按需生成的。
想象一下你需要处理 100 万个数据,如果你先把它们全部存在 Vec 里,会占用大量内存。但如果你写一个自定义迭代器,它只需要记录“当前在哪”,内存占用几乎为零,无论你要处理多少数据。
🛠️ 进阶:带状态的过滤迭代器
你可以创建一个包装另一个迭代器的自定义迭代器。
#![allow(unused)]
fn main() {
struct SkipNone<I> {
inner: I,
}
impl<I, T> Iterator for SkipNone<I>
where
I: Iterator<Item = Option<T>>, // 要求内部迭代器产出 Option
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
// 循环调用内部迭代器,直到找到一个 Some 或者结束
while let Some(opt) = self.inner.next() {
if let Some(val) = opt {
return Some(val);
}
}
None
}
}
}
核心总结:自定义三部曲
- 定义结构体:确定你需要哪些字段来记住迭代进度(比如索引、当前数值等)。
- 声明
Item类型:告诉 Rust 迭代器产出什么(引用&T还是值T)。 - 编写
next逻辑
- 如果有数据:更新状态,返回
Some(value)。 - 如果没数据:返回
None。
七、 高级与特殊迭代器
- DoubleEndedIterator:双端迭代器,支持从后往前迭代(rev())
- ExactSizeIterator:已知精确长度的迭代器(len())
- FusedIterator:融合迭代器,保证 None 之后永远返回 None
- Peekable:支持“预览”下一个元素而不消耗它
八、 性能与底层原理
- 迭代器的内部迭代 vs 外部迭代
- 编译器优化:循环展开(Loop Unrolling)与内联
- 迭代器在内存安全上的保障:避免索引越界
- itertools 库:了解社区标准扩展包
九、 迭代器与生命周期的交集
- 迭代器产生的引用寿命约束
- 在结构体中存储迭代器(涉及生命周期参数)
- 闭包捕获环境对迭代器生命周期的影响