Rust 中的 `for...in` 循环:简洁迭代的艺术

在 Rust 中,for...in 循环是遍历集合的核心工具。它看似简单,却暗藏着 Rust 对内存安全和所有权的精妙设计。本文将聚焦 for...in 循环的本质,解析它如何通过迭代器实现灵活的集合遍历,以及不同迭代方式的选择逻辑。

本质:迭代器的“语法糖”

for...in 循环的核心逻辑是迭代一个迭代器(Iterator)。Rust 中所有能被 for...in 遍历的类型(如向量、数组、范围等),都实现了 IntoIterator 特性——这个特性的作用是将类型“转换为迭代器”。

简单说,for item in 集合 本质上是在做:

  1. 调用集合的 into_iter() 方法,将其转换为迭代器;
  2. 依次从迭代器中取出元素,赋值给循环变量 item
  3. 直到迭代器耗尽,循环结束。

三种基础遍历方式:从引用到所有权

针对集合的不同使用场景(只读、修改、消耗),for...in 有三种典型写法,对应三种迭代器类型:

1. 只读遍历:迭代不可变引用

当你需要访问集合元素但不修改它们时,用 &集合 触发不可变引用迭代:

1
2
3
4
5
6
7
let vec = vec![1, 2, 3];
// 等价于 for item in vec.iter()
for item in &vec {
    println!("{}", item); // item 类型是 &i32(不可变引用)
}
// 遍历后 vec 仍可使用(未被消耗)
println!("遍历后:{:?}", vec); // [1, 2, 3]

这里的 &vec 会通过 IntoIterator 转换为 vec.iter() 返回的迭代器,每次迭代获取的是元素的不可变引用(&T)。由于只借用元素,原集合不会被修改或消耗。

为什么用 iter() 而不是 iter_mut()
iter() 产生的不可变引用可以共享,不影响其他地方对集合的只读访问。而 iter_mut() 产生的可变引用具有排他性——迭代期间整个集合会被独占借用,其他地方无法同时访问(包括读取)。因此,仅需读取时用 iter() 更灵活。

2. 修改遍历:迭代可变引用

若要修改集合元素,用 &mut 集合 触发可变引用迭代:

1
2
3
4
5
6
7
let mut vec = vec![1, 2, 3];
// 等价于 for item in vec.iter_mut()
for item in &mut vec {
    *item += 1; // item 类型是 &mut i32(可变引用,需解引用修改)
}
// 遍历后 vec 已被修改
println!("修改后:{:?}", vec); // [2, 3, 4]

&mut vec 会转换为 vec.iter_mut() 返回的迭代器,每次迭代获取元素的可变引用(&mut T)。通过可变引用,我们可以直接修改集合中的元素,且原集合仍可继续使用。

3. 消耗遍历:迭代所有权

当你需要获取元素的所有权(例如将元素转移到其他地方),直接用 集合 触发所有权迭代:

1
2
3
4
5
6
7
let vec = vec![1, 2, 3];
// 等价于 for item in vec.into_iter()
for item in vec {
    println!("{}", item); // item 类型是 i32(直接拥有元素)
}
// 遍历后 vec 已被消耗,无法再使用
// println!("{:?}", vec); // 编译错误:vec 已被移动

直接使用 vec 时,会调用 vec.into_iter() 生成迭代器,每次迭代会将元素的所有权从集合中“取出”并转移给 item。遍历结束后,原集合因失去所有元素的所有权而被“消耗”,无法再被访问。

为什么“不需要修改时,也可能需要消耗遍历?
“不需要修改”仅意味着不需要改变元素的值,但可能需要转移元素的所有权

  • 例如将元素存入另一个集合(需拥有所有权才能存入);
  • 对于整数等 Copy 类型,消耗遍历可避免反复解引用(直接使用值更简洁);
  • 处理临时集合时,“用完即弃”可节省内存。

示例:

1
2
3
4
5
6
7
let vec = vec![1, 2, 3];
let mut new_vec = Vec::new();
// 消耗原集合,将元素所有权转移到新集合
for item in vec {
    new_vec.push(item); 
}
println!("新集合:{:?}", new_vec); // [1, 2, 3]

特殊场景:范围迭代

除了集合,for...in 还能直接遍历“范围(Range)”,这是一种无需提前创建集合的轻量迭代方式:

1
2
3
4
5
6
7
8
9
// 遍历 1 到 4(不包含 5)
for i in 1..5 {
    println!("{}", i); // 1, 2, 3, 4
}

// 遍历 1 到 5(包含 5)
for i in 1..=5 {
    println!("{}", i); // 1, 2, 3, 4, 5
}

范围本质上也是一种迭代器,它通过计算生成序列,无需占用额外内存。

总结:三种迭代方式的核心区别

迭代方式 元素类型 是否消耗原集合 适用场景
iter() &T(不可变引用) 不消耗 只读访问,需保留原集合,允许共享访问
iter_mut() &mut T(可变引用) 不消耗 需要修改元素,独占集合访问权
into_iter() T(所有权) 消耗 需获取元素所有权(转移、Copy 类型简化操作等),用完即弃集合

选择的核心逻辑是:是否需要修改元素,以及是否需要保留原集合。掌握这三种方式,就能灵活应对 Rust 中绝大多数集合遍历场景。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy