你刚接触 Rust,多半会在 for 循环之外第一次撞上「迭代器」这个词,然后被 iter、iter_mut、into_iter 三个长得几乎一样的方法搞晕。其实 Rust 迭代器的核心只有一句话:迭代器是按顺序产出元素的值序列,三种方法只是决定「借用还是拿走」元素的所有权;再加上适配器组合,就能用一条链式调用写完过滤、映射和求和。本文基于 Rust 1.7x 稳定版,把四种常见写法从最小示例讲到边界,并标出最容易踩的所有权坑。如果你已经写过其他语言的循环,可以先在脑子里记下「只读、可改、拿走」这三个档位,下文会逐一对照。

一、先看结论
四种写法对应四种「访问元素的方式」,先选对再写代码:
| 写法 | 访问方式 | 能否改元素 | 是否转移所有权 |
|---|---|---|---|
iter() |
不可变借用 &T |
否 | 否 |
iter_mut() |
可变借用 &mut T |
是 | 否 |
into_iter() |
获取所有权 T |
是 | 是 |
| 适配器 + 消费器 | 链式组合(map/filter/collect) |
取决于上游 | 取决于上游 |
一句话:只读用 iter,要改原数组用 iter_mut,要拿走元素用 into_iter,要一次性变换并收集用适配器链。
二、iter() 不可变借用迭代
iter() 返回每个元素的不可变引用,适合只读遍历,是日常用得最多的一种 rust 迭代器 写法。它不碰原集合的所有权,循环结束之后集合照常可用,所以只要不需要修改元素,默认就选它。
fn main() {
let nums = vec![1, 2, 3];
for x in nums.iter() {
println!("{}", x);
}
// nums 依然可用
println!("{:?}", nums);
}
x 的类型是 &i32,你只能读不能改,循环结束后 nums 原封不动。这是最安全的默认选择,只要不需要修改元素就优先用 iter()。如果你熟悉 Python 的迭代协议,可以先过一遍 Python3 教程 找找感觉,两者的「遍历但不转移」理念是相通的。
一个容易忽略的点:iter() 产出的是引用,所以闭包里要拿到值往往要解引用。比如 nums.iter().sum::<i32>() 能直接求和,但 nums.iter().map(|x| x * 2) 里的 x 是 &i32,需要 |&x| 或 *x 才能当数字用。
三、iter_mut() 可变借用迭代
当你要在遍历时就地修改元素,用 iter_mut() 拿到可变引用 &mut T。它同样不转移所有权,只是临时借出来改,循环一结束借用释放,原集合原地更新。
fn main() {
let mut nums = vec![1, 2, 3];
for x in nums.iter_mut() {
*x *= 2;
}
println!("{:?}", nums); // [2, 4, 6]
}
注意 iter_mut 可变 的写法里必须解引用 *x 才能赋值。所有权仍然留在 nums 上,循环结束后数组被原地改好。如果和 C++ 的 STL 迭代器概念类比,可以先看 C++ 教程 的容器章节做对照,思路一致,只是 Rust 把「借用可不可变」写进了类型里。
和 iter() 的区别只在一处:一个是 &T 只读,一个是 &mut T 可写。编译器会在编译期拦住「用 iter() 却试图改元素」的错误,这正是 Rust 安全性的来源。
四、into_iter() 获取所有权迭代
into_iter() 会把元素的所有权交出来,遍历之后原集合不能再使用。它常用于「消费」一个集合,比如把 String 拆成字符 owned 值,或者把 Vec 里的元素逐个移进另一个结构。
fn main() {
let words = vec!["hi".to_string(), "yo".to_string()];
for w in words.into_iter() {
println!("{}", w);
}
// words 已被消费,这里再使用会编译报错
}
into_iter 方法 拿到的是 T 而不是引用,所以适合元素本来就要被移动走的场景;如果你只是想读,用 iter() 更省心。这里有个直觉:把集合当「容器」,iter() 是隔着玻璃看、iter_mut() 是伸手进去改、into_iter() 是直接把东西拿出来归自己。

五、迭代器适配器与消费器(组合写法)
第四种写法不是新方法,而是把前面三种和「适配器 + 消费器」组合起来,用一条惰性求值 的链完成变换。
fn main() {
let nums = vec![1, 2, 3, 4, 5];
let sum: i32 = nums.iter()
.filter(|&&x| x % 2 == 1) // 迭代器适配器:只留奇数
.map(|&x| x * x) // 迭代器适配器:平方
.sum(); // 消费器:求和
println!("{}", sum); // 1 + 9 + 25 = 35
}
这里 filter 和 map 都是迭代器适配器,它们本身不立即执行,而是等到 sum 这个消费器被调用时才真正跑起来,这就是惰性求值。关键点:iter() 产出 &i32,所以闭包里要用 |&x| 解一层引用。
5.1 踩坑清单
| 现象 | 原因 | 修复 |
|---|---|---|
iter() 里改元素报借用错误 |
不可变借用禁止修改 | 改用 iter_mut() 并解引用 *x |
into_iter() 后原集合不可用 |
所有权已被转移 | 只是遍历就改用 iter() 保留所有权 |
| 适配器链没效果 | 没有消费器触发执行 | 末尾加 collect() / sum() 等消费器 |
常见错误是把适配器当成「立刻算完」的管道。记住:没有消费器,前面的 map/filter 一句都不会跑。这既是性能优势(按需计算、可短路),也是新手最常漏掉的一步。

动手验证:编译运行四种写法
理论要落地,建议把四种写法各写一遍、真正编译运行(操作)。把示例存成 iter_demo.rs,执行命令编译:
rustc iter_demo.rs -o iter_demo && ./iter_demo
运行后检查输出:iter 只打印、iter_mut 打印翻倍后的值、into_iter 之后原集合不可用,验证所有权确实按预期转移。预期结果是三类输出各不相同,且 into_iter 后再用原集合会编译报错。如果报借用错误,按 5.1 清单修复:多半是 iter() 里试图改元素,或 into_iter() 后误用了已消费的集合。边界情况要注意:闭包里拿 &i32 必须 |&x| 解引用,否则算的是引用地址而非数值;版本(Rust 1.7x 稳定版)之间语义一致,可放心对照。适用场景上,只读遍历用 iter、就地改原数据用 iter_mut、要移动元素所有权用 into_iter,选错档位就会踩编译期错误。
总结
Rust 迭代器一共四种常见写法:iter() 只读借用、iter_mut() 可变借用、into_iter() 拿走所有权,以及用适配器加消费器做链式变换。记住三句话:只读选 iter、改原数据选 iter_mut、要移动元素选 into_iter;凡是用适配器一定要以消费器收尾,否则惰性求值不会真正执行。下一步建议把这三种方法在同一个 Vec 上各写一遍,体会所有权在编译期的不同约束。系统学 Rust 可以回到 Rust 教程,从所有权一章开始打地基。
延伸学习
想继续深入 Rust,这两篇值得一看:
- Go 与 Rust 全面对比 帮你从另一门语言的角度理解 Rust 的设计取舍;
- 发现 Rust 之美 整理了适合初学者的入门资料。
常见问题
Q:iter、iter_mut 和 into_iter 到底怎么选?
A:看你想对元素做什么。只读取用 iter(),得到的是不可变引用;要原地修改用 iter_mut(),拿到可变引用;要把元素的所有权从集合里移走、集合本身不再需要,用 into_iter()。记不住就默认用 iter()。
Q:为什么我写了 map 却没有生效?
A:map 是迭代器适配器,本身是惰性求值,不会立刻执行,必须跟一个消费器(如 collect、sum、for 循环)才会真正跑。把 map 换成 for 循环遍历,或在末尾加 .collect::<Vec<_>>() 就能看到结果。
Q:into_iter 之后原集合还能用吗?
A:不能。into_iter 会把元素的所有权转移出来,原集合已经被消费,再次使用会在编译期报错。如果还需要原集合,改用 iter() 保留所有权即可。

TRAE-AI编程



