迭代
在最初的几个练习中,你已经了解到 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
是除了 for
和 while
外的另一种循环结构。
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]); } }
更快。当然,也有例外:编译器有时能证明即使手动索引也不会越界,因此也会移除边界检查。但一般而言,尽可能选择迭代而非索引。