自定义 Counter
结构体类型,并实现迭代器。其他语言的场景,读取数据库行数据时,使用的就是迭代器。我们使用for
语言遍历数组,也是一种迭代。
结构体对象实现 Iterator
trait,创建自定义的迭代器,只需要实现一个next
方法的定义。它会在每次调用时返回一个包裹在Some
中的迭代器元素,并在迭代器结束时返回None
。
Item
定义为关联类型,就像是给类型起了一个别名。
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 3 {
return Some(self.count);
}
None
}
}
#[test]
fn calling_next_directly() {
let mut count = Counter::new();
assert_eq!(count.next(), Some(1));
assert_eq!(count.next(), Some(2));
assert_eq!(count.next(), None);
}
这个迭代器并没有特别的地方,某种意义上它就是实现了一个接口,外部可以将这个对象当做接口来看待,最终体现在无脑适用迭代器提供的模式方法。
例子中的new
方法称为关联函数associated function
,将其命名为函数而不是方法,是因为它不会作用于某个具体的结构体实例,方法的参数声明中也不接受self
,但它依然声明在impl
块中。
new
类似于构造函数,用来实例化一个新的结构体类型。通过类型名后面追加::
来调用关联函数。单元测试的例子calling_next_directly
声明了Counter
实例并手动调用next
方法。
实现了迭代器,如果只是为了手动调用next
方法,那没啥意义。关键是依赖RUST
提供的各种模式方法来链式处理迭代器。
迭代器抽象封装了很多处理模式,也就是迭代器适配器iterator adaptor
方法,用来将现有的迭代器转换为其它不同类型的迭代器,通过链式地调用多个迭代适配器来完成一些复杂的操作。
下面的单元测试通过迭代器构造了一个[(1, 2)]
元组。zip
方法会在两个迭代器中任意一个返回None
是结束迭代,skip
跳过了第一个迭代,collect
返回一个配对后值的集和。
#[test]
fn using_other_iterator_trait_methods() {
let s: Vec<(u32, u32)> = Counter::new().zip(Counter::new().skip(1)).collect();
println!("{:?}", s)
}
RUST
正是有通过这个适配器,给我们抽象出了很多处理模式,我们通过简单的链式调用就可以实现很多复杂的能力。下面的方法介绍,也可以快速浏览官方查看。
collect
方法将一个迭代器转换为集合,只不过collect
推断不出我们最终想要的类型,需要我们明确指定 collect
返回值的类型。上个例子中的Vec<(u32, u32)>
必须明确的指定类型,否则编译器会报错。
collect
文档中还提供了另一种指定类型的方式:“turbofish::<>”,调整之后的代码会变成下面这个样子,结合编译器给出的类型提示,理解迭代器链路上上对象声明。
#[test]
fn using_other_iterator_trait_methods() {
let s = Counter::new()
.zip(Counter::new().skip(1))
.collect::<Vec<(u32, u32)>>();
println!("{:?}", s)
}
在collect
返回集合的基础上还可以继续迭代,继续生成新的集合。下面的代码示例,我们基于第一次collect
生成的元素[(1, 2)]
,重新生成一个新的集合[(2, 4)]
。
#[test]
fn using_other_iterator_trait_methods() {
let s: Vec<(u32, u32)> = Counter::new()
.zip(Counter::new().skip(1))
.collect::<Vec<(u32, u32)>>()
.iter()
.map(|x| (x.0 * 2, x.1 * 2))
.collect();
println!("{:?}", s)
}