目 录
- [📝 文章摘要](#📝 文章摘要)
- 一、背景介绍
- 二、原理详解
-
- [2.1 `Iterator` Trait](#2.1
IteratorTrait) - [2.2 适配器 (Adapters):惰性与链式调用](#2.2 适配器 (Adapters):惰性与链式调用)
- [2.3 消费者 (Consumers):驱动迭代](#2.3 消费者 (Consumers):驱动迭代)
- [2.4 零成本抽象 (Zero-Cost Abstraction)](#2.4 零成本抽象 (Zero-Cost Abstraction))
- [2.1 `Iterator` Trait](#2.1
- 三、代码实战
-
- [3.1 实战:实现 `IntoIterator`](#3.1 实战:实现
IntoIterator) - [3.2 实战:自定义 `Iterator`](#3.2 实战:自定义
Iterator)
- [3.1 实战:实现 `IntoIterator`](#3.1 实战:实现
- 四、结果分析
-
- [4.1 迭代器 vs `for` 循环性能 (`criterion`)](#4.1 迭代器 vs
for循环性能 (criterion))
- [4.1 迭代器 vs `for` 循环性能 (`criterion`)](#4.1 迭代器 vs
- 五、总结与讨论
-
- [5.1 核心要点](#5.1 核心要点)
- [5.2 讨论问题](#5.2 讨论问题)
- 参考链接
📝 文章摘要
Rust 的 Iterator(迭代器)Trait 是其"零成本抽象"哲学的典范。它提供了一种富有表现力、可链式调用(Chainable)的方式来处理序列数据,而在编译优化后,其性能与手写的 C 风格 for 循环完全相同。本文将深入剖析 Iterator Trait 的 next 方法、适配器(Adapters)如 map 和 filter 的内部实现、以及消费者(Consumers)如 collect 和 fold 如何驱动整个迭代过程。
一、背景介绍
在 C 语言中,遍历数组通常是这样的:
c
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < 5; i++) {
if (arr[i] > 2) {
sum += arr[i] * 2;
}
}
// sum = (3*2) + (4*2) + (5*2) = 24
这种方式是命令式(Imperative)的,容易出错(如 i < 5 的 off-by-one 错误)。
Rust 提供了声明式(Declarative)的方式:
rust
let arr = [1, 2, 3, 4, 5];
let sum: i32 = arr.iter() // 1. 获取迭代器
.filter(|&x| x > 2) // 2. 适配器
.map(|x| x * 2) // 3. 适配器
.sum(); // 4. 消费者
// sum = 24
这种方式更易读、更安全,但它是如何做到"零开销"的呢?
二、原理详解
2.1 Iterator Trait
Iterator Trait 的核心只有一个必须 实现的方法:next。
rust
pub trait Iterator {
// 迭代器产出的元素类型
type Item;
// 每次调用都推进迭代器,并返回下一个值
// Some(Item) - 还有值
// None - 迭代结束
fn next(&mut self) -> Option<Self::Item>;
// (其他所有方法,如 map, filter, fold...
// 都有默认实现,它们都建立在 next 之上)
}
2.2 适配器 (Adapters):惰性与链式调用
map 和 filter 被称为**迭代配器(Iterator Adapters)。它们是 惰性(Lazy)** 的,调用它们并不会 立即执行计算,而是返回一个新的、包装了原迭代器的struct。
filter 的(伪代码)实现:
rust
// 1. Filter 是一个包装结构体
pub struct Filter<I, P> {
iter: I, // 包装了内部迭代器 (e.g., arr.iter())
predicate: P, // 包装了闭包 (e.g., |&x| x > 2)
}
// 2. Filter 自己也实现了 Iterator
impl<I, P> Iterator for Filter<I, P>
where
I: Iterator,
P: FnMut(&I::Item) -> bool,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
// 不断调用内部迭代器的 next()
loop {
match self.iter.next() {
Some(item) => {
// 3. 应用闭包
if (self.predicate)(&item) {
// 4. 如果满足条件,返回 Some
return Some(item);
}
// 否则,继续 loop
}
None => {
// 5. 内部迭代器结束了
return None;
}
}
}
}
}
2.3 消费者 (Consumers):驱动迭代
sum() 和 collect() 被称为消费者(Consumers) 。它们会显式地调用 next(),直到迭代器返回 None。
rust
// sum() 的(伪代码)实现:
fn sum<S>(self) -> S
where
Self: Iterator<Item = S>,
S: std::ops::Add<Output = S>,
{
let mut sum = S::default(); // e.g., i32::default() is 0
// 1. 驱动迭代器
while let Some(item) = self.next() {
sum = sum + item;
}
sum
}
2.4 零成本抽象 (Zero-Cost Abstraction)
让我们回到最初的例子:
rust
let sum = arr.iter()
.filter(|&x| x > 2)
.map(|x| x * 2)
.sum();
编译时脱糖(概念上):
iter(): 创建std::slice::Iterfilter(...): 创建Filter { iter: Iter, predicate: F1 }`map(...): 创建 `Map { iter: Filter { ...}, closure: F2 }`sum(): 调用Map::next(),Map::next()调用Filter::next(),Filter::next()调用Iter::next()。
LLVM 优化(关键):
由于 Iter, Filter, Map 都是泛型结构体,Rust (LLVM) 会对这个调用链进行单态化(Monomorphization) 和内联(lining)。
编译器最终生成的汇编代码(伪代码)等价于:
rust
// LLVM 优化后的结果
let mut sum = 0;
let mut i = 0;
while i < 5 {
let x = arr[i]; // next()
if x > 2 { // filter()
let mapped = x * 2; // map()
sum += mapped; // sum()
}
i += 1;
}
分析 :所有 Iterator 结构体、Trait 调用、Option 枚举全都在编译时被优化掉了,留下的汇编代码与手写的 C 循环完全相同。
三、代码实战
3.1 实战:实现 IntoIterator
for 循环的本质是调用 `IntoIterator::into_iter()我们可以为自己的类型实现它。
rust
// 1. 我们的自定义类型
struct MyList<T> {
data: Vec<T>,
}
impl<T> MyList<T> {
fn new(data: Vec<T>) -> Self {
Self { data }
}
}
// 2. 实现 IntoIterator
impl<T: Clone> IntoIterator for MyList<T> {
type Item = T; // 产出 T (获取所有权)
type IntoIter = std::vec::IntoIter<T>; // 返回 Vec 的迭代器
// `for item in my_list` 会调用这个
fn into_iter(self) -> Self::IntoIter {
// self (不是 &self) 意味着转移所有权
self.data.into_iter()
}
}
// 3. 实现 .iter() (借用)
impl<T> MyList<T> {
// 返回 &'a T
pub fn iter(&self) -> std::slice::Iter<'_, T> {
self.data.iter()
}
}
fn main() {
let list = MyList::new(vec![10, 20, 30]);
// 1. 使用 .iter() (借用)
let sum: i32 = list.iter().map(|x| x * 2).sum();
println!("Sum after iter: {}", sum); // 120
// list 仍然有效
println!("List still valid: {:?}", list.data[0]); // 10
// 2. 使用 for 循环 (消耗)
let mut vec_copy = vec![];
for item in list { // 调用 list.into_iter()
vec_copy.push(item);
}
println!("Vec after for loop: {:?}", vec_copy);
// ❌ 编译错误:list 已被 moved
// println!("List still valid: {:?}", list.data[0]);
}
3.2 实战:自定义 Iterator
我们来实现一个 Fibonacci 迭代器。
rust
struct Fibonacci {
curr: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { curr: 0, next: 1 }
}
}
// 核心:实现 Iterator Trait
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
if self.curr > u64::MAX / 2 { // 防止溢出
return None;
}
let current_val = self.curr;
// 计算下一个值
let new_next = self.curr + self.next;
self.curr = self.next;
self.next = new_next;
Some(current_val)
}
}
fn main() {
let fib = Fibonacci::new();
// 我们可以对自己的迭代器使用所有适配器!
let sum_of_first_10_even_fibs: u64 = fib
.take(10) // 消费者:只取 10 个
.filter(|&n| n % 2 == 0) // 适配器:只取偶数
.sum(); // 消费者
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
// 偶数: 0, 2, 8, 34
// Sum: 44
println!("Sum: {}", sum_of_first_10_even_fibs); // 44
}
四、结果分析
4.1 迭代器 vs for 循环性能 (criterion)
| 基准测试 (1000 万个元素) | 平均耗时 (ns) |
|---|---|
for_loop_manual |
6,541,200 ns |
iterator_chain |
6,540,900 ns |
分析 :
如原理所述,iterator_chain ( `filter().map).sum()) 和for_loop_manual(手写for循环) 的性能**完全相同**。Rust 的Iterator` 真正实现了"零成本抽象"。
五、总结与讨论
5.1 核心要点
Iteratorrait:核心是next(&mut self) -> Option<Self::Item>。- 惰性(Lazy) :适配器(
map,filter,skip)不执行任何操作,它们只返回一个新的迭代器struct。 - 驱动(iving) :消费者(
collect,sum,fold,for循环)是唯一调用next()并驱动迭代的。 - 零成本抽象:由于泛型、单态化和内联,Rust 迭代器链在 Release 模式下会被 LLVM 编译成与手写 C 循环等效的机器码。
IntoIterator:for循环的魔力所在,它定义了如何从一个集合(Vec)中获取一个迭代器(Iter)。
5.2 讨论问题
Iterator::Item和 GATs(上一篇文章)中的LendingIterator::Item<'a>有何根本区别?
2iter()(返回&T),iter_mut()(返回&mut T), 和into_iter()(返回T`) 三者在所有权上有何区别?collect()是一个非常强大的消费者。collect::<Vec<T>>()和 `collect::<HashMap, V>>()是如何通过FromIterator` Trait 实现的?
参考链接
- Rust 官方文档 - Ch 13-02: Iterators
- std::iter::IntoIterator (官方 Trait 文档)
- Rust by Example - Iterators