迭代器
ch13-02-iterators.md
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
迭代器是 Rust 中的一个模式,它允许你对一个项的序列进行某些处理。例如。列表 13-5 中对 vecctor 中的每一个数加一:
let v1 = vec![1, 2, 3];
let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, [2, 3, 4]);
vector 的iter
方法允许从 vector 创建一个迭代器(iterator)。接着迭代器上的map
方法调用允许我们处理每一个元素:在这里,我们向map
传递了一个对每一个元素x
加一的闭包。map
是最基本的与比较交互的方法之一,因为依次处理每一个元素是非常有用的!最后collect
方法消费了迭代器并将其元素存放到一个新的数据结构中。在这个例子中,因为我们指定v2
的类型是Vec<i32>
,collect
将会创建一个i32
的 vector。
像map
这样的迭代器方法有时被称为迭代器适配器(iterator adaptors),因为他们获取一个迭代器并产生一个新的迭代器。也就是说,map
在之前迭代器的基础上通过调用传递给它的闭包来创建了一个新的值序列的迭代器。
概括一下,这行代码进行了如下工作:
- 从 vector 中创建了一个迭代器。
- 使用
map
适配器和一个闭包参数对每一个元素加一。 - 使用
collect
适配器来消费迭代去并生成了一个新的 vector。
这就是如何产生结果[2, 3, 4]
的。如你所见,闭包是使用迭代器的很重要的一部分:他们提供了一个自定义类似map
这样的迭代器适配器的行为的方法。
迭代器是惰性的
在上一部分,你可能已经注意到了一个微妙的用词区别:我们说map
适配(adapts)了一个迭代器,而collect
消费(consumes)了一个迭代器。这是有意为之的。单独的迭代器并不会做任何工作;他们是惰性的。也就是说,像列表 13-5 的代码但是不调用collect
的话:
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1); // without collect
这可以编译,不过会给出一个警告:
warning: unused result which must be used: iterator adaptors are lazy and do
nothing unless consumed, #[warn(unused_must_use)] on by default
--> src/main.rs:4:1
|
4 | v1.iter().map(|x| x + 1); // without collect
| ^^^^^^^^^^^^^^^^^^^^^^^^^
这个警告是因为迭代器适配器实际上并不自己进行处理。他们需要一些其他方法来触发迭代器链的计算。我们称之为消费迭代器(consuming adaptors),而collect
就是其中之一。
那么如何知道迭代器方法是否消费了迭代器呢?还有哪些适配器是可用的呢?为此,让我们看看Iterator
trait。
Iterator
trait
迭代器都实现了一个标准库中叫做Iterator
的 trait。其定义看起来像这样:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
这里有一些还未讲到的新语法:type Item
和Self::Item
定义了这个 trait 的关联类型(associated type),第XX章会讲到关联类型。现在所有你需要知道就是这些代码表示Iterator
trait 要求你也定义一个Item
类型,而这个Item
类型用作next
方法的返回值。换句话说,Item
类型将是迭代器返回的元素的类型。
让我们使用Iterator
trait 来创建一个从一数到五的迭代器Counter
。首先,需要创建一个结构体来存放迭代器的当前状态,它有一个u32
的字段count
。我们也定义了一个new
方法,当然这并不是必须的。因为我们希望Counter
能从一数到五,所以它总是从零开始:
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
接下来,我们将通过定义next
方法来为Counter
类型实现Iterator
trait。我们希望迭代器的工作方式是对当前状态加一(这就是为什么将count
初始化为零,这样迭代器首先就会返回一)。如果count
仍然小于六,将返回当前状态,不过如果count
大于等于六,迭代器将返回None
,如列表 13-6 所示:
# struct Counter {
# count: u32,
# }
#
impl Iterator for Counter {
// Our iterator will produce u32s
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// increment our count. This is why we started at zero.
self.count += 1;
// check to see if we've finished counting or not.
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
type Item = u32
这一行表明迭代器中Item
的关联类型将是u32
。同样无需担心关联类型,因为第XX章会涉及他们。
next
方法是迭代器的主要接口,它返回一个Option
。如果它是Some(value)
,相当于可以迭代器中获取另一个值。如果它是None
,迭代器就结束了。在next
方法中可以进行任何迭代器需要的计算。在这个例子中,我们对当前状态加一,接着检查其是否仍然小于六。如果是,返回Some(self.count)
来产生下一个值。如果大于等于六,迭代结束并返回None
。
迭代器 trait 指定当其返回None
,就代表迭代结束。该 trait 并不强制任何在next
方法返回None
后再次调用时必须有的行为。在这个情况下,在第一次返回None
后每一次调用next
仍然返回None
,不过其内部count
字段会依次增长到u32
的最大值,接着count
会溢出(在调试模式会panic!
而在发布模式则会折叠从最小值开始)。