Rust 迭代器怎么用?4 种常见写法一次讲清,从入门到实战

编程狮(w3cschool.cn) 2026-10-12 17:38:40 浏览数 (73)
反馈

在 Rust 里遍历集合不要先想着写 for 加下标,标准库给了三种可直接调用的迭代器:iter 只读借用、into_iter 拿走所有权、iter_mut 可变借用,再配合 map/filter 这类适配器就能链式处理。一句话:想只读用 iter,想消费集合用 into_iter,想边遍历边改元素用 iter_mut,处理完再用 collect 收尾。下面先用一张表看清四种写法,再逐个给出可编译示例。掌握它们后,你就能写出既安全又零额外拷贝的遍历代码,告别手动下标越界的风险。

Rust 迭代器四种写法封面

先看结论

你的目的 写法 所有权处理 适用度
只读遍历集合 for x in v.iter() 借用,不移动 最常用
消费集合拿走元素 for x in v.into_iter() 拿走所有权 中
遍历并修改元素 for x in v.iter_mut() 可变借用 中
链式转换后收集 v.iter().map(...).collect() 借用后收集 高,惰性

记住一条铁律:迭代器是惰性的,只有遇到 collect、sum、for 循环这类“消费”动作才会真正执行;写了 map/filter 却没消费,编译器不会报错,但什么也不会发生。

一、迭代器到底是什么

掌握 Rust 迭代器用法,先要理解 Iterator trait。Rust 的迭代器本质是实现了 Iterator trait 的类型,核心只有一个方法 next,每次返回 Some(项) 或 None。你写的 for 循环,底层就是编译器把它改写成对 next 的反复调用。理解这一点很关键:迭代器本身不持有数据,它只是按规则“一个个吐出”集合里的元素。

迭代器最大的特点是惰性求值:像 map、filter 这样的适配器只是组装出一条新的迭代器管线,并不会立刻计算,要等到被 for 循环或 collect 消费时才真正跑起来。这也意味着链式写法几乎没有额外性能负担,因为编译器会把整条管线优化成一轮循环。英文里这套机制叫 rust iterator,核心是 next 方法。关于 Iterator trait 与 trait 系统的基础,建议先过一遍 Rust 教程。

Rust 迭代器四种写法怎么选?

迭代器的适用边界很清晰:只读场景用 iter,可变场景用 iter_mut,要转移所有权用 into_iter。性能上三者差异极小,瓶颈通常在你自己的闭包里;安全上借用规则在编译期就拦住了“同时可变与不可变”的冲突;版本上从 1.0 至今 API 稳定;边界是迭代器消费后原集合的状态会随写法不同而变化,这点最容易踩坑。看 rust_lang 文档 能拿到最完整的适配器清单。

二、写法一:iter 只读遍历

iter 返回集合中每个元素的不可变借用,遍历过程中原集合保持不变,也最常用。

let v = vec![1, 2, 3];
for x in v.iter() {
    println!("{}", x);
}
// v 在这里依然可用

失败排查:若你写 for x in v.iter() 之后又尝试 v.push(4),会因为 iter 借用了 v 而编译失败,报 cannot borrow v as mutable because it is also borrowed as immutable。修复方式是用 iter_mut 或在遍历结束后再 push。命令(编译)执行后预期正常打印,可用 cargo build 验证是否报错。比起 Python 里随手 for,Python 的迭代更宽松,Rust 则把借用冲突挡在编译期。

三、写法二:into_iter 消费所有权

into_iter 把集合“消费”掉,每次 next 拿走一个元素的所有权,遍历结束后原集合不再存在。

let v = vec![String::from("a"), String::from("b")];
for s in v.into_iter() {
    println!("{}", s);
}
// 这里再使用 v 会编译失败,v 已被移动

这种写法适合“拿到集合、处理完就丢”的场景,比如把 Vec 转成另一种结构。C++ 的 range-based for 不会转移容器所有权,靠拷贝或引用,与 Rust 的借用模型形成鲜明对比。失败场景:若还想在循环后使用 v,会报 borrow of moved value: v,修复就是改用 iter 保留所有权,或先 clone 再消费。

四、写法三:iter_mut 可变遍历与适配器链

iter_mut 返回可变借用,让你在遍历时就地修改元素,常用于把集合里的每个值翻倍、清洗字符串等。

let mut v = vec![1, 2, 3];
for x in v.iter_mut() {
    *x *= 2;
}
// v 现在是 [2, 4, 6]

最常见的高级写法是把适配器串起来,再用 collect 收尾:

let v = vec![1, 2, 3, 4];
let evens: Vec<i32> = v.iter()
    .filter(|&&x| x % 2 == 0)
    .map(|&x| x * x)
    .collect();
// evens = [4, 16]

这里 map 和 filter 都是惰性的,collect 才是触发计算的“消费”动作。失败排查:若只写了 v.iter().map(|x| x * 2) 却没 collect,编译能通过但什么都不会发生,因为迭代器从未被消费。运行 cargo build 检查不会有错,但运行 cargo run 验证才会发现没输出,这是惰性求值最典型的坑。Go 语言没有这样的迭代器适配器链,多用 for-range 配手写逻辑;参考 Go 教程 看差异。

补充一个高频技巧:遍历时如果同时需要下标,不要自己维护计数器,直接用 enumerate 适配器,它会把每个元素包成 (索引, 值) 的元组:

let v = vec!["a", "b", "c"];
for (i, x) in v.iter().enumerate() {
    println!("第 {} 个是 {}", i, x);
}

这样索引和元素一起拿到,既不会越界,也省去手写 i += 1 的麻烦。需要注意 enumerate 同样惰性,必须配合 for 或 collect 才会执行。

本文示例基于 Rust 官方文档与 1.70 版本推断(推断),代码为可编译的简化写法。

总结

Rust 迭代器常见四种写法:iter 只读借用、遍历后原集合仍在,最常用;into_iter 消费所有权、遍历完集合被移动,适合处理完就丢;iter_mut 可变借用、能在遍历时就地改元素;再配合 map/filter 等适配器与 collect 收尾,可写出无额外拷贝的链式处理。记住铁律:迭代器是惰性的,必须被 for 或 collect 这类消费动作触发才会执行,只写适配器不消费什么都不会发生。性能上三者都很轻,安全上借用检查在编译期拦下非法并发修改。

延伸学习

常见问题

Q:iter、into_iter 和 iter_mut 到底有什么区别?

三者区别只在所有权与可变性:iter 返回不可变借用,原集合保留;into_iter 拿走所有权,遍历后集合被消费;iter_mut 返回可变借用,可在遍历时修改元素。只读统计用 iter,处理完就丢用 into_iter,就地修改用 iter_mut。

Q:为什么我只写了 map 却没有任何结果?

因为 map 是惰性适配器,它只是描述“下一步怎么转换”,并不会真的计算。必须接一个消费动作——比如 collect 收集成新集合、或 for 循环、或 sum 这类方法——迭代器才会真正执行。只写 map 不消费,编译能通过但运行时什么都不做。

Q:迭代器链式写法会影响运行性能吗?

基本不会。Rust 编译器会把整条迭代器管线(map、filter、collect)优化成一轮紧凑循环,没有中间集合的额外分配,性能和手写 for 循环接近甚至更好。真正的开销通常来自你闭包里的逻辑,而不是迭代器本身。

0 人点赞