闭包 Closure
闭包是一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值,例如:
rust
fn main() {
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
}
上面的代码展示了非常简单的闭包 sum
,它拥有一个入参 y
,同时捕获了作用域中的 x
的值,因此调用 sum(2)
意味着将 2(参数 y
)跟 1(x
)进行相加,最终返回它们的和:3
。
可以看到 sum
非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。
Rust 闭包在形式上借鉴了 Smalltalk
和 Ruby
语言,与函数最大的不同就是它的参数是通过 |parm1|
的形式进行声明,如果是多个参数就 |param1, param2,...|
, 下面给出闭包的形式定义:
rust
|param1, param2,...| {
语句1;
语句2;
返回表达式
}
如果只有一个返回表达式的话,定义可以简化为:
rust
|param1| 返回表达式
下面展示了同一个功能的函数和闭包实现形式:
rust
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
注意:闭包中最后一行表达式返回的值,就是闭包执行后的返回值。
迭代器 Iterator
迭代器允许我们迭代一个连续的集合,例如数组、动态数组 Vec
、HashMap
等,在此过程中,只需关心集合中的元素如何处理,而无需关心如何开始、如何结束、按照什么样的索引去访问等问题。
IntoIterator
特征拥有一个 into_iter
方法,因此我们还可以显式的把数组转换成迭代器:
rust
let arr = [1, 2, 3];
for v in arr.into_iter() {
println!("{}", v);
}
迭代器之所以成为迭代器,就是因为实现了 Iterator
特征 ,要实现该特征,最主要的就是实现其中的 next
方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item
。
例子:模拟实现 for 循环
因为 for
循环是迭代器的语法糖,因此我们完全可以通过迭代器来模拟实现它:
rust
let values = vec![1, 2, 3];
{
let result = match IntoIterator::into_iter(values) {
mut iter => loop {
match iter.next() {
Some(x) => { println!("{}", x); },
None => break,
}
},
};
result
}
IntoIterator::into_iter
是使用完全限定的方式去调用 into_iter
方法,这种调用方式跟 values.into_iter()
是等价的。
同时我们使用了 loop
循环配合 next
方法来遍历迭代器中的元素,当迭代器返回 None
时,跳出循环。
可以使用 into_iter
的方式将数组转化为迭代器,除此之外,还有 iter
和 iter_mut
,它们的区别如下:
into_iter
会夺走所有权iter
是借用iter_mut
是可变借用
Iterator 和 IntoIterator 的区别
这两个其实还蛮容易搞混的,但我们只需要记住,Iterator
就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next
。
而 IntoIterator
强调的是某一个类型如果实现了该特征,它可以通过 into_iter
,iter
等方法变成一个迭代器。
使用 collect
收集成 HashMap
集合:
rust
use std::collections::HashMap;
fn main() {
let names = ["sunface", "sunfei"];
let ages = [18, 18];
let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();
println!("{:?}",folks);
}
zip
是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)>
这样的新的迭代器,在此处就是形如 [(name1, age1), (name2, age2)]
的迭代器。
然后再通过 collect
将新迭代器中(K, V)
形式的值收集成 HashMap<K, V>
,同样的,这里必须显式声明类型,然后 HashMap
内部的 KV
类型可以交给编译器去推导,最终编译器会推导出 HashMap<&str, i32>
,完全正确!
闭包作为适配器参数
之前的 map
方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值:
rust
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
filter
是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 s
是来自迭代器中的值,然后使用 s
跟外部环境中的 shoe_size
进行比较,若相等,则在迭代器中保留 s
值,若不相等,则从迭代器中剔除 s
值,最终通过 collect
收集为 Vec<Shoe>
类型。
实现 Iterator 特征
之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 HashMap
。 你也可以创建自己的迭代器 ------ 只要为自定义类型实现 Iterator
特征即可。
首先,创建一个计数器:
rust
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
我们为计数器 Counter
实现了一个关联函数 new
,用于创建新的计数器实例。下面我们继续为计数器实现 Iterator
特征:
rust
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
首先,将该特征的关联类型设置为 u32
,由于我们的计数器保存的 count
字段就是 u32
类型, 因此在 next
方法中,最后返回的是实际上是 Option<u32>
类型。
每次调用 next
方法,都会让计数器的值加一,然后返回最新的计数值,一旦计数大于 5,就返回 None
。
最后,使用我们新建的 Counter
进行迭代:
rust
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);
实现 Iterator 特征的其它方法
可以看出,实现自己的迭代器非常简单,但是 Iterator
特征中,不仅仅是只有 next
一个方法,那为什么我们只需要实现它呢?因为其它方法都具有默认实现,所以无需像 next
这样手动去实现,而且这些默认实现的方法其实都是基于 next
方法实现的。
下面的代码演示了部分方法的使用:
rust
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);
其中 zip
,map
,filter
是迭代器适配器:
zip
把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将形如[1, 2, 3, 4, 5]
和[2, 3, 4, 5]
的迭代器合并后,新的迭代器形如[(1, 2),(2, 3),(3, 4),(4, 5)]
map
是将迭代器中的值经过映射后,转换成新的值[2, 6, 12, 20]filter
对迭代器中的元素进行过滤,若闭包返回true
则保留元素[6, 12],反之剔除
而 sum
是消费者适配器,对迭代器中的所有元素求和,最终返回一个 u32
值 18
。