跳到主要内容

迭代器

问题

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_mapmap + flatten.flat_map(|x| x.chars())
flatten展平嵌套vec![vec![1,2], vec![3]].into_iter().flatten()
peekable可查看下一个元素.peekable() 后用 .peek()
cloned / copied&TT.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: 迭代器的惰性求值有什么好处?

答案

  1. 避免中间集合分配filter().map() 不会创建中间 Vec,而是逐元素流式处理
  2. 短路求值take(3) 只处理前 3 个满足条件的元素,后面的不会被计算
  3. 可组合:适配器可以自由组合成复杂的处理管道
  4. 编译器优化:惰性链让编译器有机会将整个管道优化为单次循环

Q2: iter()iter_mut()into_iter() 的区别?

答案

方法产出类型是否消耗集合for 循环等价
iter()&Tfor x in &v
iter_mut()&mut Tfor x in &mut v
into_iter()Tfor 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 的类型——包括 VecStringHashMapHashSetBTreeMapResult<Vec<T>, E> 等。

Q4: foldreduce 有什么区别?

答案

// 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("失败")

这是一个非常实用的模式——批量处理时遇到任何一个失败都提前返回。

相关链接