迭代

在最初的几个练习中,你已经了解到 Rust 允许你使用 for 循环遍历集合。当时我们处理的是范围(例如 0..5),但同样的规则也适用于数组和向量这样的集合。

#![allow(unused)]
fn main() {
// 对于 `Vec` 有效
let v = vec![1, 2, 3];
for n in &v {
    println!("{}", n);
}

// 对于数组也有效
let a: [u32; 3] = [1, 2, 3];
for n in a.iter() {
    println!("{}", n);
}
}

现在,是时候了解这背后的原理了。

for 循环的展开

每次你在 Rust 中编写 for 循环时,编译器都会将其转换为如下代码:

#![allow(unused)]
fn main() {
let mut iter = v.into_iter();
loop {
    match iter.next() {
        Some(n) => {
            println!("{}", n);
        }
        None => break,
    }
}
}

loop 是除了 forwhile 外的另一种循环结构。
loop 块会无限循环,除非你明确地使用 break 退出它。

Iterator 特性

前面代码片段中的 next 方法来自 Iterator 特性。 Iterator 特性在 Rust 标准库中定义,为能够产生一系列值的类型提供了一个共享接口:

#![allow(unused)]
fn main() {
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
}

Item 关联类型指定了由迭代器产生的值的类型。

next 返回序列中的下一个值。
如果有值返回,则返回 Some(value);如果没有,则返回 None

请注意:迭代器返回 None 并不能保证它已被耗尽。只有当迭代器实现了更严格的 FusedIterator 特性时,才会有此保证。

IntoIterator 特性

并非所有类型都实现了 Iterator,但许多类型可以转换为实现了该特性的类型。
这就是 IntoIterator 特性的作用:

#![allow(unused)]
fn main() {
trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;
    fn into_iter(self) -> Self::IntoIter;
}
}

into_iter 方法消耗原始值并返回其元素的迭代器。
一个类型只能有一个 IntoIterator 的实现:对于 for 应该展开为什么形式不存在任何歧义。

一个小细节:任何实现了 Iterator 的类型也会自动实现 IntoIterator。它们只是从 into_iter 返回自己!

边界检查

迭代迭代器有一个很好的副作用:设计上你不可能越界。
这让 Rust 能够从生成的机器代码中移除边界检查,从而加快迭代速度。

换言之,

#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
for n in v {
    println!("{}", n);
}
}

通常比

#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
for i in 0..v.len() {
    println!("{}", v[i]);
}
}

更快。当然,也有例外:编译器有时能证明即使手动索引也不会越界,因此也会移除边界检查。但一般而言,尽可能选择迭代而非索引。