Rust 的一个特点是函数式编程,可以将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。闭包和迭代器是其中两个显著特点
闭包
闭包(closures)是可以保存在一个变量中或作为参数传递给其他函数的匿名函数,可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算,闭包允许捕获被定义时所在作用域中的值。
可以使用 ||
包裹闭包中的参数,如果有多于一个参数,可以使用逗号分隔,比如 |param1, param2|
,这个 let
语句意味着 expensive_closure
包含一个匿名函数的定义,
//定义闭包
let expensive_closure = |num| {
num
};
//使用闭包
expensive_closure(intensity)
闭包不要求像 fn
函数那样在参数和返回值上注明类型,因为函数是暴露给用户的显式接口的一部分,而闭包储存在变量中并被使用,不用命名他们或暴露给库的用户调用。
闭包定义会为每个参数和返回值推断一个具体类型,如下面的例子,如果尝试调用闭包两次,第一次使用 String
类型作为参数而第二次使用 u32
,则会得到一个错误:
let example_closure = |x| x;
//将闭包推断为String
let s = example_closure(String::from("hello"));
//运行失败
let n = example_closure(5);
如果代码需要多次运行闭包,可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,如下:字段 value
是 Option<u32>
类型的。在执行闭包之前,value
将是 None
。如果使用 Cacher
的代码请求闭包的结果,这时会执行闭包并将结果储存在 value
字段的 Some
成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 Some
成员中的结果。
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}
我们保存一个新的 Cacher
实例来存放闭包。接着,在每一个需要结果的地方,调用 Cacher
实例的 value
方法。然而这样的设计。在第一次调用之后,就不能在更新,因此可以尝试修改 Cacher
存放一个哈希 map 而不是单独一个值,哈希 map 的 key 将是传递进来的 arg
值,而 value 则是对应 key 调用闭包的结果值。
闭包与环境捕获
闭包可以作为内联匿名函数来使用,但闭包还有另一个函数所没有的功能:他们可以捕获其环境并访问其被定义的作用域的变量。如下:即便 x
并不是 equal_to_x
的一个参数,equal_to_x
闭包也被允许使用变量 x
,因为它与 equal_to_x
定义于相同的作用域。
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用:
这三种捕获值的方式被编码为如下三个 Fn
trait:
FnOnce
消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的Once
部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。FnMut
获取可变的借用值所以可以改变其环境Fn
从其环境获取不可变的借用值
当创建一个闭包时,Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 FnOnce
。那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 FnMut
,而不需要对被捕获的变量进行可变访问的闭包则也实现了 Fn
。
例如:equal_to_x
闭包不可变的借用了 x
(所以 equal_to_x
具有 Fn
trait),因为闭包体只需要读取 x
的值;
如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move
关键字,如下:x
被移动进了闭包,因为闭包使用 move
关键字定义。接着闭包获取了 x
的所有权,同时 main
就不再允许在 println!
语句中使用 x
了。去掉 println!
即可修复问题
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
//这是不允许的
println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
迭代器
迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
在 Rust 中,迭代器是惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果,一旦创建迭代器之后,可以选择用多种方式利用它,比如使用循环进行遍历
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
迭代器都实现了一个叫做 Iterator
的定义于标准库的 trait,其中,Item
类型将是迭代器返回元素的类型,next
是 Iterator
实现者被要求定义的唯一方法。next
一次返回迭代器中的一个项,封装在 Some
中,当迭代器结束时,它返回 None
。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
直接在迭代器上调用 next
方法可以消耗迭代器里一个元素,如下的例子:定义一个可变的 v1_iter
,它是一个vector
的迭代器,在迭代器上调用 next
方法改变了迭代器中用来记录序列位置的状态
#[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);
}
注意 : 使用 for
循环时无需使 v1_iter
可变因为 for
循环会获取 v1_iter
的所有权并在后台使 v1_iter
可变。
从 next
调用中得到的值是 vector 的不可变引用。iter
方法生成一个不可变引用的迭代器。如果我们需要一个获取 v1
所有权并返回拥有所有权的迭代器,则可以调用 into_iter
而不是 iter
。如果我们希望迭代可变引用,则可以调用 iter_mut
而不是 iter
。
迭代器的api
Iterator
trait 有一系列不同的由标准库提供默认实现的方法
- 迭代器的创建
迭代器的构造有多种方式
use std::slice::Iter;
use std::iter::Iterator;
let it = 0..10;//[0,9]的整数
let it = 0..=10;//[0,10]的整数
let it = 0..;// 产生从0开始的无限整数流(迭代器)
let it = [1,2,3,4].iter();
let it = vec![5; 3].iter();
// chain()将两个迭代器顺序拼接合并
let it = (0..10).chain(20..30);
- 迭代器的遍历
可以使用while
, for
等多种方式遍历迭代器
//while-next模式
while let Some(x) = iter.next() {
print!("{:?} ", x);
}
// for-in循环对迭代器进行遍历
for i in it {
print!("{} ", i);
}
// for_each()对元素逐个处理,内部调用的是fold(),
[1,2,3,4].iter().for_each(|x| {
print!("{x} ");//1 2 3
});
// 元素个数跟个数较少的迭代器相同
let a = [1,2,3].iter();
let b = ['a', 'b', 'c', 'd'].iter();
let x = a.zip(b);
//(1, 'a')(2, 'b')(3, 'c')
- 迭代器获取元素
除了 next
逐个获取元素之外,还可以通过多种方式更加灵活的获得迭代器的下一个元素:
take(k)
取前面k个元素,只可调用一次
assert_eq!(vec![1,2,3], (1..10).take(3).collect::<Vec<_>>());
nth(k)
取得迭代器剩余元素中第k个位置的元素,位置从0开始;之后,迭代器跳转到下一个位置
let mut it = [1, 2, 3].iter();
assert_eq!(Some(&1), it.nth(0));
assert_eq!(Some(&2), it.nth(0));
assert_eq!(Some(3), (0..4).nth(3));
last()
只取最后一个元素,只能调用一次。
assert_eq!((1..4).last(), Some(3));
- 迭代器的变换
rev()
对迭代器进行反转
vec![0, 1, 2, 3, 4].iter().rev().for_each(|x|print!("{x},"));
//输出:9,8,7,6,5,4,3,2,1,0,
skip(k)
使得跳过k个元素
assert_eq!(vec![2,3], (0..4).skip(2).collect::<Vec<_>>());
step_by(k)
,从第一个元素开始,每k个取一个出来
//0 2 4 6 8 10
(0..=10).step_by(2).for_each(|x| print!("{x} "));
assert_eq!(vec![0,2,4,6], (0..7).step_by(2).collect::<Vec<_>>());
zip()
将2个迭代器合并为一对一元组迭代器
let a = [1,2,3].iter();
let b = ['a', 'b', 'c', 'd'].iter();
let x = a.zip(b);
//(1, 'a')(2, 'b')(3, 'c')
map
方法使用闭包来调用每个元素以生成新的迭代器,如下的例子,用闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
迭代器的 filter
方法获取一个使用迭代器的每一个项并返回布尔值的闭包,如果闭包返回 true
,其值将会包含在 filter
提供的新迭代器中。如果闭包返回 false
,其值不会包含在结果迭代器中。
let s = "1 a 2 b 3 c";
let a = s.split_ascii_whitespace().filter_map(|x|x.parse::<i32>().ok()).collect::<Vec<i32>>();
//[1, 2, 3]
- 求值结算
sum
获取迭代器的所有权并反复调用 next
来遍历迭代器,因而会消费迭代器,当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和
assert_eq!([1,2,3].iter().sum::<i32>(), 6);
max
将会返回一系列值的最大值,而 min
将返回最小值
assert_eq!([1,2,3].iter().max(), Some(&3));
assert_eq!([1,2,3].iter().min(), Some(&1));
count
可以输出迭代器内部元素的个数
assert_eq!([1,2,3].iter().count(), 3);
all
判断迭代器中是否所有元素都符合闭包predicate
指定的测试。
let b = (2..10).into_iter().all(|i|i>0);
println!("{}", b);//true
fold()
方法,通过传入一个初始值和一个函数操作,对迭代器中的每一个元素依次进行处理,最后返回结果。
assert_eq!(3, (1..3).fold(0, |acc, x|acc+x));//1+2
assert_eq!(6, (1..3).fold(0, |acc, x|acc+2*x));//2*1 + 2*2
- collect
collect
方法可以消费迭代器并将结果收集到一个数据结构中,要注意,收集到的数据结构需要优先进行定义指明
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]);
创建自定义迭代器
正如之前提到的,迭代器的定义中唯一要求提供的方法就是 next
方法。一旦定义了它,就可以使用所有其他由 Iterator
trait 提供的拥有默认实现的方法来创建自定义迭代器了,如下是自定义的一个迭代器,它首先返回 1。如果 count
值小于 6,next
会返回封装在 Some
中的当前值,不过如果 count
大于或等于 6,迭代器会返回 None
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> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
#[test]
fn calling_next_directly() {
let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
}
通过定义 next
方法实现 Iterator
trait,我们现在就可以使用任何标准库定义的拥有默认实现的 Iterator
trait 方法了,因为他们都使用了 next
方法的功能。
#[test]
fn using_other_iterator_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);
}