首页 / Rust 入门教程 / 迭代器(Iterators)

Rust 入门教程

迭代器(Iterators)

本教程共 78 篇 · 第 49 篇 · 更新于 2026-08-08 · 约 11 分钟阅读

RustRust 入门教程迭代器Iteratornextinto_iteriteriter_mut

本节目标:理解迭代器如何遍历集合、为什么是惰性的、next 方法怎么工作,并分清 into_iter/iter/iter_mut 与”消费者/适配器”的分工。

资深 Rust 开发者常会说:写 Rust 最该掌握的之一就是迭代器。它让你只关心”每个元素怎么处理”,不用管从哪开始、到哪结束、按什么索引访问。

1-1 for 循环其实就是迭代器

Rust 里的 for 循环,本质是对迭代器的语法糖:

let arr = [1, 2, 3];
for v in arr {
    println!("{v}");
}

它不像别的语言用索引 arr[i] 去访问,而是把 arr 当迭代器,直接逐个取出元素。数组本身不是迭代器,但它实现了 IntoIterator trait,for 糖自动把它转成迭代器。

也可以显式转换:

let arr = [1, 2, 3];
for v in arr.into_iter() {
    println!("{v}");
}

范围也能直接迭代:for i in 1..10 就是迭代 19

1-2 惰性初始化

迭代器是惰性的:光创建它,啥也不会发生,只有真正用起来才开始遍历。

let v1 = vec![1, 2, 3];
let v1_iter = v1.iter(); // 此刻没有任何遍历发生

for val in v1_iter {
    println!("{val}");
}

for 之前,只是造了个迭代器,零开销、不消耗元素。只有 for 启动,迭代才真正进行。这种惰性保证了”创建迭代器”这个动作本身几乎不花代价。

1-3 next 方法:迭代器的心脏

迭代器之所以是迭代器,是因为它实现了 Iterator trait,而这个 trait 最核心的方法就是 next

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

next 每次取出下一个元素,包装在 Option 里:有值返回 Some(元素),没了返回 Nonefor 循环其实就是不停地调 next

你也可以自己调:

fn main() {
    let arr = [1, 2, 3];
    let mut arr_iter = arr.into_iter();

    assert_eq!(arr_iter.next(), Some(1));
    assert_eq!(arr_iter.next(), Some(2));
    assert_eq!(arr_iter.next(), Some(3));
    assert_eq!(arr_iter.next(), None);
}

注意三点:

  • next 返回 Option,有值 Some(T)、无值 None
  • 元素是按顺序依次取出的;
  • 手动调 next 必须把迭代器声明成 mut,因为调用会改动内部状态(当前位置)。for 循环不用标 mut,它内部帮你处理了。
Note

next 对元素是消耗性的:每调一次吃掉一个,最后迭代器空了,只能返回 None。所以同一个迭代器不能无限制重复遍历。

1-4 into_iter、iter、iter_mut 三兄弟

把集合变迭代器,有三种方式,区别在于”怎么借”:

  • into_iter():拿走集合的所有权,遍历后原集合没了;
  • iter()不可变借用,遍历的是引用 &T
  • iter_mut()可变借用,拿到 &mut T,能改元素。
fn main() {
    let values = vec![1, 2, 3];
    for v in values.into_iter() {
        println!("{v}");
    }
    // 这里再用 values 会报错,所有权已被转移

    let values = vec![1, 2, 3];
    let _it = values.iter();
    println!("{:?}", values); // 借用,原集合照常用

    let mut values = vec![1, 2, 3];
    let mut it_mut = values.iter_mut();
    if let Some(v) = it_mut.next() {
        *v = 0; // v 是 &mut i32,解引用后修改
    }
    println!("{:?}", values); // [0, 2, 3]
}

记忆窍门:into_ 开头多半是拿走所有权,_mut 结尾多半是可变借用,剩下就是不可变借用。

Tip

IteratorIntoIterator 别搞混:IntoIterator 表示”我能变成一个迭代器”(靠 into_iter);Iterator 表示”我就是迭代器,能调 next”。迭代器自身也实现了 IntoIterator,所以链式 .into_iter().into_iter() 不会报错。

1-5 消费者适配器:吃掉迭代器,给个值

迭代器上的方法分两类。第一类叫消费者适配器,它会消耗迭代器,返回一个具体的值——因为它们内部都靠调 next 来取元素。最常见的就是 sum

fn main() {
    let v1 = vec![1, 2, 3];
    let total: i32 = v1.iter().sum();
    assert_eq!(total, 6);
    println!("{:?}", v1); // 借用迭代器,原 v1 仍可用
}

sum 拿了迭代器的所有权,遍历求和后返回总数。调用后迭代器就没了,不能再 for 它。

1-6 迭代器适配器:返回新迭代器

第二类叫迭代器适配器,它不消耗,而是返回一个新的迭代器。这是链式调用的关键:v.iter().map(...).filter(...)

但适配器是惰性的——光写不消费,啥也不干:

let v1 = vec![1, 2, 3];
v1.iter().map(|x| x + 1); // 警告:什么都没发生!

必须用消费者适配器收尾。比如 collect 把结果收成集合:

let v1 = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);

map(|x| x + 1) 把每个元素加 1,但只是”规划”了一下;collect 一收尾,规划才真正执行,最终得到新向量。collect 很强大,能收成 VecHashMap 等多种集合,所以要标类型告诉它收成啥。

filter 也是适配器,按闭包条件保留元素:

let v = vec![1, 2, 3, 4];
let evens: Vec<_> = v.into_iter().filter(|x| x % 2 == 0).collect();
assert_eq!(evens, vec![2, 4]);

1-7 适配器链为什么是零成本

你可能会担心:mapfilter 一层层包起来,会不会产生很多临时对象和函数调用开销?答案是不会。迭代器适配器是”惰性”的——map 返回的只是一个新的迭代器结构体,它内部记下”下一步要套用这个闭包”,并不会立刻跑任何元素。

只有当 collectsumfor 这类消费者出现,整条链才真正启动:next() 每被调用一次,前面各个适配器就地依次作用在同一元素上。再加上单态化,编译器为这套具体类型生成了专用代码,并能把闭包内联、把循环优化到几乎和手写 for 一样。所以”迭代器写法”和”手写循环”在性能上持平,却往往更易读。

Note

因为适配器不分配堆内存、不存中间集合,一条长链的内存占用非常小。这正是 Rust 鼓励用迭代器改写循环的底气:表达力强,还不付运行时代价。

1-8 常用适配器与消费者一览

迭代器的方法多到记不住,但常用的就那几个,按”做什么”分类记最快。适配器(返回新迭代器、惰性):map 变换每个元素、filter 按条件留元素、take(n) / skip(n) 截取前/后、chain 拼接两条、rev 反转、zip 配对、enumerate 带下标。

消费者(吃掉迭代器、给出结果):collect 收成集合、sum / product 求和求积、count 计数、min / max 最值、fold 自定义累积、any / all 判断、find 找第一个、last 取末尾。

let v = [1, 2, 3, 4, 5];
let s: i32 = v.iter().filter(|&&x| x % 2 == 1).map(|x| x * x).sum();
// 奇数 1,3,5 的平方和 = 1+9+25 = 35
Tip

记不住没关系,写的时候按”我要变换、要筛选、要配对、最后要个结果”去翻这几个名字就行。它们是积木,拼出你想要的流水线。

1-9 再看清 for 循环与迭代器的关系

很多人以为 for 循环和迭代器是两套东西,其实 for 循环的底层就是迭代器。写 for x in v 时,编译器会自动把 v 变成 v.into_iter(),然后不断调用 next(),直到返回 None。也就是说:

let v = vec![1, 2, 3];
// 下面两种完全等价
for x in &v { println!("{x}"); }

let mut it = v.iter();
while let Some(x) = it.next() { println!("{x}"); }

明白这点后,“迭代器不是什么魔法”就落地了:它只是把”取下一个元素”这件事做成了可组合、可惰性、可单态化的接口。你用 map/filter 不过是提前把”每一步怎么处理”拼好,最后由 forcollect 这样的消费者去真正驱动 next()

Tip

以后再看到迭代器链式写法,脑子里把它还原成”一个带预处理步骤的 for 循环”,理解成本和心智负担都会降下来。

1-10 小结

  • for 循环是迭代器的语法糖;迭代器由实现了 IntoIterator 的集合转换而来;
  • 迭代器是惰性的:创建不做事,使用才遍历;
  • next 是核心,返回 Option,消耗性地逐个取元素;
  • into_iter 拿所有权、iter 借用、iter_mut 可变借用;
  • 消费者适配器(如 sum)吃掉迭代器给个值;迭代器适配器(如 map/filter)返回新迭代器,需消费者收尾;
  • collect 把迭代结果收集成集合,要标目标类型。

下一章我们把平时写的 for 循环,改写成迭代器风格,体会两种写法的差别。