13 Rust语言中的函数式语言功能:迭代器与闭包
函数式编程风格通常包括将函数作为另一个函数的参数、返回值,将函数作为值赋值给变量,以供后续执行
本章中我们将会介绍以下内容:
闭包:一个可以存储在变量里的类似函数的数据结构
迭代器:一种处理元素序列的方式
如何使用这些功能来改进第十二章的I/O项目
这两个功能的性能(剧透警告:它们的速度超乎你的想象)
我40米的大刀已经饥渴难耐了,让我们攻下这一章!
13.2 使用迭代器处理元素序列
迭代器模式允许我们对一个序列的项进行某些处理。迭代器负责遍历序列中的每一项和决定序列和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑
在Rust中,迭代器是惰性的,这意味着在调用方法使用迭代器之前它都不会有效果
let v1 = vec![1,2,3];
let v1_iter = v1.iter();
在for循环中使用迭代器,迭代器减少了重复代码并消除了潜在混乱
fn main(){
let v1 = vec![1,2,3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("got: {}",val);
}
}
另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像vector这样可索引的数据结构,让我们看看迭代器是如何做到这些的
Iterator trait 和next方法
迭代器都实现了一个叫做Iterator的定义于标准库的trait,这个trait看起来如下:
pub fn trait Iterator{
type Item;
fn next(&mut self)->Option<Self::Item>;
//此处省略了方法的默认实现
}
这里有一个新语法:type Item和 Self::Item,他们定义了trait的关联类型,这个随后我们会讲到,这里我们只需知道这段代码表明要实现 Iterator,必须定义一个Item类型,这个Item类型被当作next方法的返回值类型,换言之,Item类型将是迭代器返回元素的类型
next 是Iterator实现者被要求定义的唯一方法,next一次返回一个项,封装在Some中,当迭代器结束时,它返回None
可以直接调用迭代器的方法
#[test]
fn iterator_demonstration() {
let v1 = vec![1,2,3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(),Some(&1));
assert_eq!(v1_iter.next(),Some(&2));
assert_eq!(v1_iter.next(),Some(&3));
assert_eq!(v1_iter.next(),None);
}
running 1 test
test iterator_demonstration ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
消费迭代器的方法
Iterator trait有很多不同的默认实现方法,它们由标准库提供,可以在标准库API文档中查看。一些方法在其定义中调用了next方法,这也就是为什么在实现Iterator trait时要求实现next方法的原因
这些调用next方法的方法被称为消费适配器,因为调用它们会消耗迭代器,典型的方法是sum,这个方法获取迭代器的所有权并反复调用next来遍历迭代器,因而会消费迭代器
#[test]
fn iterator_sum() {
let v1 = vec![1,2,3];
let mut v1_iter = v1.iter();
let total:i32 = v1_iter.sum();
assert_eq!(total,6);
}
running 1 test
test iterator_sum ... ok
产生其他迭代器的方法
Iterator trait中定义了另一类方法,被称为迭代适配器,他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果
如下列子,该map方法使用闭包来调用每个元素以生成心的迭代器,这里的闭包创建了一个新的迭代器,对其中vector中的每个元素都被+1
#[test]
fn add_one() {
let v1:Vec<i32> = vec![1,2,3];
let v2:Vec<_>=v1.iter().map(|x|x+1).collect();
assert_eq!(v2,vec![2,3,4]);
}
running 1 test
test add_one ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
使用闭包获取环境
现在我们介绍了迭代器,让我们展示一个通过filter迭代器适配器和捕获环境的闭包的常规用例
#[derive(PartialEq,Debug)]
struct Shoe{
size:u32,
style:String,
}
fn shoes_in_my_size(shoes:Vec<Shoe>,shoe_size:u32)->Vec<Shoe> {
shoes.into_iter()
.filter(|s|s.size == shoe_size)
.collect()
}
#[test]
fn filters_by_size(){
let shoes = vec![
Shoe{size:10,style:String::from("sneaker")},
Shoe{size:13,style:String::from("sandal")},
Shoe{size:10,style:String::from("boot")},
];
let in_my_size = shoes_in_my_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe{size:10,style:String::from("sneaker")},
Shoe{size:10,style:String::from("boot")},
]
);
}
running 1 test
test filters_by_size ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
实现Iterator trait 来创建自定义迭代器
我们可以在vector上调用iter、iter_iter或者iter_mut来创建一个迭代器,也可以用标准库中其他的集合类型创建迭代器,比如哈希map。另外,可以实现Iterator trait 来创建任何我们希望的迭代器。正如之前所提到的,定义中唯一要求的提供的方法就是next方法,一旦定义了它,就可以是用所有其他由Iterator trait提供的拥有默认实现的方法来创建自定义迭代器了
作为展示,我们创建一个从1数到的迭代器,首先我们创建一个结构体存放一些值,接着实现Iterator trait将这个结构体放入迭代器中并在此实现中使用其值
struct Counter {
count:u32,
}
impl Counter {
fn new()-> Counter{
Counter{count:0}
}
}
创建一个结构体和关联函数,接着我们为Counter类型实现 Iterator trait,通过定义next方法来指定使用迭代器时的行为
impl Iterator for Counter {
type Item = u32;
fn next(&mut self)->Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
}else {
None
}
}
}
这里将迭代器的关联类型Item 设置为 u32,意味着迭代器会返回u32值集合。再一次,这里仍无需担心关联类型
我们希望迭代器对其内部状态加一,这也就是为何将count初始化为0:我们希望迭代器首先返回1,如果count值小于6,next会返回封装在Some中的当前值,不过如果count大于或者等于6,迭代器会返回None
使用Counter 迭代器的next方法
一旦实现了Iterator trait,我们就有了一个迭代器!
#[test]
fn calling_next_directory(){
let mut counter = Counter::new();
assert_eq!(counter.next(),Some(1));
assert_eq!(counter.next(),Some(2));
assert_eq!(counter.next(),Some(3));
assert_eq!(counter.next(),Some(4));
assert_eq!(counter.next(),Some(5));
assert_eq!(counter.next(),None);
}
running 1 test
test calling_next_directory ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
测试通过!
使用自定义的迭代器中其他 Iterator trait方法
通过定义next方法实现Iterator trait,我们现在就可以使用任何标准库定义的拥有默认实现的Iterator trait方法了,因为它们都使用了next方法功能
#[test]
fn using_other_interator_trait_methods(){
let sum:u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a,b)| a*b)
.filter(|x| x%3 == 0)
.sum();
assert_eq!(18,sum)
}
running 1 test
test using_other_interator_trait_methods ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
使用自定义的Counter迭代器的多种方法
注意:zip值产生4对值:理论上第五对值(5,None)从未被产生,因为zip在任一输入迭代器返回None时也返回None
所有这些方法调用都是可能的,因为我们指定了next方法如何工作,而标准库则提供了其他调用了next的方法的默认实现