迭代器是标准的函数式编程风格,具有表达性的优势。不过 Rust 的迭代器经 过了认真设计,可以确保编译器也把它们编译为优化的机器码

Iterator和IntoIterator特 型

迭代器指的是任何实现 std::iter::Iterator 特型的值

trait Iterator {     type Item;     fn next(&mut self) -> Option<Self::Item>;     ... // 许多默认方法 } 

如果有一种自然的方式可以迭代某种类型,那它可以实现 std::iter::IntoIterator。它的 into_iter 方法接收一个值,然后基于这个值返回一个迭代器:

trait IntoIterator where Self::IntoIter::Item == Self::Item {     type Item;     type IntoIter: Iterator;     fn into_iter(self) -> Self::IntoIter; } 

创建迭代器

iter和iter_mut方法

大多数集合类型提供了 iter 和 iter_mut 方法,返回该类型的迭代器, 产生每个迭代项的共享或可修改引用

let path = Path::new("C:/Users/JimB/Downloads/Fedora.iso");  
let mut iterator = path.iter();  
assert_eq!(iterator.next(), Some(OsStr::new("C:")));  
assert_eq!(iterator.next(), Some(OsStr::new("Users")));  
assert_eq!(iterator.next(), Some(OsStr::new("JimB"))); 

IntoIterator实 现

如果类型实现了 IntoIterator,则可以调用它的 into_iter 方法,跟 for 循环一样

因为 for 循环会对其操作数应用 IntoIterator::into_iter,所以这 3个实现就可以支持迭代集合的共享引用或可修改引用,以及消费集合并取得其元素所有权:

for element in &collection { ... }
for element in &mut collection { ... }
for element in collection { ... }

并不是所有类型都会提供全部 3 种实现。

例如,HashSet、BTreeSet 和 BinaryHeap 就没有对可修改引用实现 IntoIterator,因为修改它们的元 素可能违背类型的不变性,比如被修改的值变成不同的散列值或者相对周 边元素做了不同排序,导致修改后把元素放到错误的位置上。

其他类型确 实有支持可修改引用的,但只是部分支持。例如,HashMap 和 BTreeMap 会产生它们值的可修改引用,但只产生它们键的共享引用

一般原则是迭代应该高效和可预测,因此相比于提供耗时或可能导致意外 行为(例如,重新计算 HashSet 元素的散列之后又会重新访问它们)的实现, Rust 选择完全忽略。

比Java优雅

IntoIterator 是 for 循环底层的基础,因此显然是必要的。但在不使用 for 循环时,favorites.iter() 要比 (&favorites).into_iter() 清晰得 多。按共享引用迭代是很常见的,所以 iter 和 iter_mut 更符合人的常识。

在泛型中:

use std::fmt::Debug;
fn dump<T, U>(t: T)
    where T: IntoIterator<Item=U>,
U: Debug
{
for u in t {
        println!("{:?}", u);
    }
}

转换迭代器

map & filter

let text = "  ponies  \n giraffes\niguanas  \nsquid".to_string();
let v: Vec<&str> = text.lines()
    .map(str::trim)
    .filter(|s| *s != "iguanas")
    .collect();
assert_eq!(v, ["ponies", "giraffes", "squid"]);

filter 迭代器的项类型是 &str,因此闭包参 数 s 的类型是 &&str。

在 collect 调用 filter 迭代器的 next 之前,不会发生真正的操作。

迭代器适配器属于零开销抽象。因为 map、filter 及其同 类方法是泛型的,所以把它们应用给迭代器会针对涉及的特定迭代器类型 特化它们的代码。这意味着 Rust 有足够的信息把每个迭代器的 next 方 法行内化到其消费者中,然后将整套代码作为一个单位翻译为机器码。因 此前面所示迭代器的 lines/map/filter 调用链,实际上与手写的如下代 码同样高效

filter_map & flat_map

filter_map 适配器与 map 类似,只是它允许闭包在迭代过程中要么转换 项(像 map 那样),要么删除项。总之就类似 filter 和 map 的组合。 它的签名如下:

fn filter_map<B, F>(self, f: F) -> some Iterator<Item=B>
    where Self: Sized, F: FnMut(Self::Item) -> Option<B>;

跟 map 的签名一样,只是这里闭包返回的是 Option,而不是 B。如果闭包返回 None,就表示从迭代中清除项;如果返回 Some(b),则 b 就 是 filter_map 迭代器产生的下一项。

可以把 flat_map 适配器看成 map 加 filter_map,只不过现在闭包不只 是会返回一项(像 map 那样)或返回零或多项(像 filter 那样),而 是会返回任意多个项的序列。flat_map 迭代器产生闭包返回序列的拼接 结果。

假设有一个表,其映射的是国家与它们的主要城市。给定一组国家,怎么遍历它们的主要城市?

use std::collections::HashMap;
 
let mut major_cities = HashMap::new();
major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]);
major_cities.insert("The United States", vec!["Portland",
"Nashville"]);
major_cities.insert("Brazil", vec!["São Paulo", "Brasília"]);
major_cities.insert("Kenya", vec!["Nairobi", "Mombasa"]);
major_cities.insert("The Netherlands", vec!["Amsterdam",
"Utrecht"]);
 
let countries = ["Japan", "Brazil", "Kenya"];
 
for &city in countries.iter().flat_map(|country|
&major_cities[country]) {
 
println!("{}", city);
}

对此,一种理解方式是对每个国家,先取得其城市的向量,然后把所有向量拼接为一个序列,再把它打印出来。

scan

scan 适配器类似于 map,区别在于它会传给闭包一个可修改的值,而且可以选择提前终止迭代。它接收一个初始状态值和一个闭包,闭包又接收一个对这个状态的可修改引用和底层迭代器的下一项。这个闭包必须返回 Option,scan 迭代器将其作为自己的下一项

let iter = (0..10)
    .scan(0, |sum, item| {
*sum += item;
if *sum > 10 {
None } else {
I
});
assert_eq!(iter.collect::<Vec<i32>>(), vec![0, 1, 4, 9, 16]);

take take_while

Iterator 特型的 take 和 take_while 适配器用于在取得一定项数之后或 闭包决定中断时终止迭代 take_while 迭代器对每一项应用 predicate, 在遇到第一个 predicate 返回 false 的项时返回 None

let message = "To: jimb\r\n\
               From: superego <editor@oreilly.com>\r\n\
               \r\n\
               Did you get any writing done today?\r\n\
               When will you stop wasting time plotting fractals?
\r\n";
for header in message.lines().take_while(|l| !l.is_empty()) {
    println!("{}" , header);
}

skip skip_while

Iterator 特型的 skip 和 skip_while 方法是对 take 和 take_while 的 补充,它们从迭代开始清除一定数量的项,或者一直清除到闭包发现一个 可以接受的项,然后将剩余项原封不动返回

for body in message.lines()
    .skip_while(|l| !l.is_empty())
    .skip(1) {
    println!("{}" , body);
}

这里使用 skip_while 跳过非空的行,但此时的迭代器还会产生空行,因 为这个闭包碰到空行会返回 false。所以此处又使用 skip 方法跳过这个 空行,这样得到的迭代器第一项就是邮件正文的第一行

peekable

Iterator 特型的 peekable 方法可以让代码在不消费下一项的情况下探测 下一项。调用这个方法可以将几乎任何迭代器转换为可探测的迭代器

rev

有些迭代器可以从序列两端取得项。可以使用 rev 适配器反转这种迭代 器

chain

chain 适配器会将一个迭代器添加到另一个适配器后面。更具体地说, i1.chain(i2) 会返回一个迭代器,该迭代器先从 i1 中提取项,取完后 再继续从 i2 中提取项。

enumerate

Iterator 特型的 enumerate 适配器可以向序列中添加连续的索引。对于 产生项为 A,B,C… 的迭代器

for (i, band) in bands.into_iter().enumerate() {
    let top = band_rows * i;

zip

zip 适配器将两个适配器组合为一个适配器,产生之前两个迭代器项的项 对,就像拉链把分开的两边拼在一起一样。

let v: Vec<_> = (0..).zip("ABCD".chars()).collect();
assert_eq!(v, vec![(0, 'A'), (1, 'B'), (2, 'C'), (3, 'D')]);

从这个意义上讲,可以把 zip 看成通用化的 enumerate。enumerate 只能 给其他序列添加索引,而 zip 可以添加任何迭代项

消费迭代器

count sum product

max min

max_by min_by

可以自己定义比较的元素

let max_index = max_heights.iter().enumerate().max_by_key(|(_, &x)| x).unwrap().0;
 
struct Person {
    name: String,
    age: u32,
}
 
let people = vec![
    Person { name: "Alice".to_string(), age: 25 },
    Person { name: "Bob".to_string(), age: 30 },
    Person { name: "Charlie".to_string(), age: 20 },
];
 
let oldest_person = people.iter().max_by_key(|person| person.age).unwrap();
println!("The oldest person is: {}", oldest_person.name);

any all

fold

fold 方法是一个通用工具,可以对迭代器产生项的整个序列执行某些累 计操作。这个方法接收一个名为 (accumulator)的初始值和一个 闭包,然后对当前累加器和迭代器的下一项重复应用闭包

let a = [5, 6, 7, 8, 9, 10];
 
assert_eq!(a.iter().fold(0, |n, _| n+1), 6);
assert_eq!(a.iter().fold(0, |n, i| n+i), 45);
assert_eq!(a.iter().fold(1, |n, i| n*i), 151200);
 
// max
assert_eq!(a.iter().fold(i32::min_value(), |m, &i| std::cmp::max(m,
 
i)),  
10);

last find

collect