何时使用 iter() 与 into_iter()?
解读
在国内 Rust 岗位面试中,这道题几乎是“必考题”。面试官想确认两点:
- 你是否真正理解 所有权转移 与 借用 的差异;
- 你是否能在 性能、内存、接口设计 三个维度做出权衡。
答不上来,会被直接打上“只写过玩具代码”的标签;答得太浅,会被追问“那如果集合元素是 String,你选哪个?”因此,必须给出 场景化、可落地的判断逻辑,而不是背定义。
知识点
- iter() —— 返回 &T 的迭代器,不消耗原集合,底层是 借用(&self)。
- into_iter() —— 返回 T 的迭代器,会转移所有权(self),底层是 Move。
- 语法糖:for 循环 默认调用 into_iter(),因此
for x in vec会把 vec move 掉;若不想失去所有权,必须显式for x in &vec(等价于 iter())。 - 性能差异:iter() 只是指针步进,零成本;into_iter() 对 Vec 会 回收底层堆内存,对 Copy 类型(如 u32)编译器会优化为 按值拷贝,无额外开销;对 非 Copy 类型(如 String)会 逐元素 Move,可能触发 二次分配。
- 接口设计:若你的函数 只需读取元素,用
impl IntoIterator<Item=&T>接受iter();若函数 需要获得元素所有权(如异步发送、转存到另一个线程),用impl IntoIterator<Item=T>并 建议调用者自行决定是否into_iter(),避免 隐式 Move 导致编译失败。
答案
先给出口诀,再给场景,最后给代码骨架,面试官最爱这种“总分总”结构。
口诀:“只读借 iter,要走所有 into;Copy 类型随便选,String 元素慎 move。”
场景一:只读遍历,后续还要用原集合
let v = vec!["Beijing", "Shanghai"];
for city in v.iter() { // &str
println!("{}", city);
}
// v 仍可用
选 iter(),因为 不丢失所有权,且 元素是 &str,无需 Move。
场景二:消费集合,把元素传到另一个线程
let v = vec!["hello".to_string(), "rust".to_string()];
std::thread::spawn(move || {
for s in v.into_iter() { // String
send_to_remote(s); // 需要所有权
}
});
选 into_iter(),因为 String 必须 Move,且 原集合后续不再使用。
场景三:封装库 API,既要灵活又要零成本
pub fn process<I, T>(iter: I)
where
I: IntoIterator<Item=T>,
T: AsRef<str>,
{
for s in iter {
println!("{}", s.as_ref());
}
}
// 调用者
let v = vec!["a", "b"];
process(v.iter().copied()); // 借用
process(v.into_iter()); // 所有权
用 IntoIterator 泛型,把选择权交给用户,库本身 零拷贝、零额外内存。
一句话总结:“后续还要用集合, iter();只要里面的值, into_iter();API 设计用泛型 IntoIterator,既安全又灵活。”
拓展思考
-
自定义集合如何同时支持 iter() 与 into_iter()?
实现impl<'a> IntoIterator for &'a MyVec返回&T迭代器,再实现impl IntoIterator for MyVec返回T迭代器。面试时能把 双 trait 实现 写出来,直接加分。 -
async 场景下为什么更倾向 into_iter()?
异步任务常常要把数据 move 到 Future,若用 iter() 会留下 生命周期约束,导致 Future 无法成为 'static,必须用into_iter()或collect::<Vec<_>>().into_iter()把数据 完全转移 到堆上,解除生命周期绑定。 -
大集合性能优化:into_iter().collect() 会复用原 Vec 的容量吗?
不会。into_iter()会把 Vec 解构为 RawVec,collect() 会 重新分配。若需 原地复用容量,应使用Vec::into_raw_parts(nightly)或 drain_filter 技巧,这是 国内大厂性能敏感模块 的常用手段,答出来可展示 底层功力。