迭代器
问题
Rust 的迭代器有什么特点?为什么说它是"零成本抽象"?
答案
Rust 的迭代器是惰性的——创建迭代器时不执行任何操作,只有在消费时才会逐元素处理。编译器会将迭代器链优化为等价的手写循环代码(零成本抽象)。
Iterator Trait
pub trait Iterator {
type Item; // 关联类型:迭代产出的元素类型
fn next(&mut self) -> Option<Self::Item>; // 核心方法
// ... 几十个默认方法(map, filter, fold 等)
}
每次调用 next() 返回 Some(item) 或 None(迭代结束)。
三种迭代方式
let v = vec![1, 2, 3];
// iter() → 产出 &T(不可变引用,不消耗集合)
for item in v.iter() { /* item: &i32 */ }
// iter_mut() → 产出 &mut T(可变引用)
let mut v = vec![1, 2, 3];
for item in v.iter_mut() { *item *= 2; }
// into_iter() → 产出 T(消耗集合,获取所有权)
for item in v.into_iter() { /* item: i32, v 不能再用 */ }
// for 循环默认调用 into_iter()
for item in &v { } // 等价于 v.iter()
for item in &mut v { } // 等价于 v.iter_mut()
for item in v { } // 等价于 v.into_iter()
迭代器适配器(Adapter)
适配器是惰性的——只是构建处理管道,不执行任何操作:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 适配器链:过滤 → 映射 → 取前 3 个
let result: Vec<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0) // 保留偶数
.map(|&x| x * x) // 平方
.take(3) // 只取前 3 个
.collect(); // ← 消费者,触发求值
println!("{:?}", result); // [4, 16, 36]
}
常用适配器:
| 适配器 | 作用 | 示例 |
|---|---|---|
map | 转换每个元素 | .map(|x| x * 2) |
filter | 过滤元素 | .filter(|x| x > &0) |
take | 取前 n 个 | .take(5) |
skip | 跳过前 n 个 | .skip(2) |
enumerate | 附加索引 | .enumerate() → (index, value) |
zip | 合并两个迭代器 | a.zip(b) → (a_item, b_item) |
chain | 串联两个迭代器 | a.chain(b) |
flat_map | map + flatten | .flat_map(|x| x.chars()) |
flatten | 展平嵌套 | vec![vec![1,2], vec![3]].into_iter().flatten() |
peekable | 可查看下一个元素 | .peekable() 后用 .peek() |
cloned / copied | &T → T | .iter().cloned() |
inspect | 调试中间值 | .inspect(|x| println!("{}", x)) |
消费者(Consumer)
消费者触发迭代器求值并产出最终结果:
let v = vec![1, 2, 3, 4, 5];
// collect:收集为集合
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
// sum / product
let total: i32 = v.iter().sum(); // 15
let product: i32 = v.iter().product(); // 120
// count / min / max
let count = v.iter().count(); // 5
let min = v.iter().min(); // Some(&1)
let max = v.iter().max(); // Some(&5)
// any / all
let has_even = v.iter().any(|x| x % 2 == 0); // true
let all_positive = v.iter().all(|x| *x > 0); // true
// find / position
let first_even = v.iter().find(|&&x| x % 2 == 0); // Some(&2)
let pos = v.iter().position(|&x| x == 3); // Some(2)
// fold / reduce
let sum = v.iter().fold(0, |acc, x| acc + x); // 15
let sum = v.iter().copied().reduce(|a, b| a + b); // Some(15)
// for_each(替代 for 循环)
v.iter().for_each(|x| println!("{}", x));
自定义迭代器
struct Counter {
current: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
fn main() {
let sum: u32 = Counter::new(5)
.zip(Counter::new(5).skip(1)) // (1,2), (2,3), (3,4), (4,5)
.map(|(a, b)| a * b) // 2, 6, 12, 20
.filter(|x| x % 3 == 0) // 6, 12
.sum(); // 18
println!("{}", sum);
}
零成本抽象
Rust 编译器将迭代器链优化为等价的手写循环。例如:
// 迭代器写法
let sum: i32 = (0..1000)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();
// 编译器优化后等价于:
let mut sum = 0;
let mut i = 0;
while i < 1000 {
if i % 2 == 0 {
sum += i * i;
}
i += 1;
}
没有中间集合分配,没有虚函数调用——完全内联。这也是 Rust 声称"高级抽象不损失性能"的实例。
常见面试问题
Q1: 迭代器的惰性求值有什么好处?
答案:
- 避免中间集合分配:
filter().map()不会创建中间 Vec,而是逐元素流式处理 - 短路求值:
take(3)只处理前 3 个满足条件的元素,后面的不会被计算 - 可组合:适配器可以自由组合成复杂的处理管道
- 编译器优化:惰性链让编译器有机会将整个管道优化为单次循环
Q2: iter()、iter_mut()、into_iter() 的区别?
答案:
| 方法 | 产出类型 | 是否消耗集合 | for 循环等价 |
|---|---|---|---|
iter() | &T | 否 | for x in &v |
iter_mut() | &mut T | 否 | for x in &mut v |
into_iter() | T | 是 | for x in v |
选择原则:不需要修改用 iter(),需要修改用 iter_mut(),不再需要集合用 into_iter()。
Q3: collect() 是如何工作的?
答案:
collect() 会调用目标集合的 FromIterator trait 实现,将迭代器的元素收集为指定类型。通常需要类型标注:
let v: Vec<i32> = (0..10).collect(); // 收集为 Vec
let s: String = vec!['a', 'b', 'c'].into_iter().collect(); // 收集为 String
let map: HashMap<_, _> = vec![(1, "a"), (2, "b")].into_iter().collect();
// turbofish 语法
let v = (0..10).collect::<Vec<i32>>();
collect() 能收集为任何实现了 FromIterator 的类型——包括 Vec、String、HashMap、HashSet、BTreeMap、Result<Vec<T>, E> 等。
Q4: fold 和 reduce 有什么区别?
答案:
// fold:需要初始值,可以返回不同类型
let sum = vec![1, 2, 3].iter().fold(0, |acc, x| acc + x); // 6
// reduce:用第一个元素作为初始值,返回 Option
let sum = vec![1, 2, 3].into_iter().reduce(|a, b| a + b); // Some(6)
let empty: Option<i32> = Vec::<i32>::new().into_iter().reduce(|a, b| a + b); // None
fold:总是返回值,空迭代器返回初始值reduce:可能返回None(空迭代器),不需要初始值
Q5: 迭代器和 for 循环哪个性能更好?
答案:
性能相同。Rust 编译器将迭代器链和 for 循环编译为等价的机器码。实际上,迭代器风格有时甚至更快,因为它能帮助编译器做更好的边界检查消除(bounds check elimination)。
Rust 的 Benchmark 显示,迭代器处理大数组的速度与手写 for 循环几乎一致——这就是"零成本抽象"的含义。
Q6: 如何将 Iterator<Item = Result<T, E>> 收集为 Result<Vec<T>, E>?
答案:
collect() 支持将 Result 迭代器收集为 Result<Vec<T>, E>——遇到第一个错误就短路返回:
let results = vec![Ok(1), Ok(2), Ok(3)];
let collected: Result<Vec<i32>, String> = results.into_iter().collect();
// Ok([1, 2, 3])
let results = vec![Ok(1), Err("失败".to_string()), Ok(3)];
let collected: Result<Vec<i32>, String> = results.into_iter().collect();
// Err("失败")
这是一个非常实用的模式——批量处理时遇到任何一个失败都提前返回。