Rust 迭代器用法(附 4 种写法),从最小示例讲到边界

编程狮(w3cschool.cn) 2026-10-13 07:03:10 浏览数 (34)
反馈

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

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

一、先看结论

四种写法对应四种「访问元素的方式」,先选对再写代码:

写法 访问方式 能否改元素 是否转移所有权
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() 是直接把东西拿出来归自己。

Rust 迭代器:用哪个?

五、迭代器适配器与消费器(组合写法)

第四种写法不是新方法,而是把前面三种和「适配器 + 消费器」组合起来,用一条惰性求值 的链完成变换。

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 一句都不会跑。这既是性能优势(按需计算、可短路),也是新手最常漏掉的一步。

Rust 迭代器常见坑

动手验证:编译运行四种写法

理论要落地,建议把四种写法各写一遍、真正编译运行(操作)。把示例存成 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,这两篇值得一看:

  1. Go 与 Rust 全面对比 帮你从另一门语言的角度理解 Rust 的设计取舍;
  2. 发现 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() 保留所有权即可。

0 人点赞