闭包
- 可以保存进变量或作为参数传递给其他函数的匿名函数;
- 可以在一个地方创建闭包,然后在不同的上下文中执行闭包运;
- 不同于函数,闭包允许捕获调用者作用域中的值。
闭包的定义
- 以一对竖线(|)开始,在竖线中指定闭包的参数;之所以选择这个语法是因为它与 Smalltalk 和 Ruby 的闭包定义类似。
- 参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。大括号之后闭包的结尾,需要用于 let 语句的分号。
闭包的类型推断和注解
- 闭包不要求像 fn 函数那样在参数和返回值上注明类型。
- 函数中需要类型注解是因为他们是暴露给用户的显式接口的一部分。但是闭包并不用于这样暴露在外的接口:他们储存在变量中并被使用,不用命名他们或暴露给库的用户调用。闭包通常很短并只与对应相对任意的场景较小的上下文中。在这些有限制的上下文中,编译器能可靠的推断参数和返回值的类型,类似于它是如何能够推断大部分变量的类型一样
带有泛型的和 Fn trait 的闭包
struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>,
}
- 在结构体中实现闭包:指定类型,实现 Fn trait bound
- Fn 系列 trait 由标准库提供。所有的闭包都实现了 trait Fn、FnMut 或 FnOnce 中的一个,用来捕获环境;
闭包捕获环境
当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销
- FnOnce:不能多次获取相同变量的所有权的事实,它只能被调用一次。
- FnMut:获取可变的借用值所以可以改变其环境;
- Fn: 从其环境获取不可变的借用值
注:
- 由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 FnOnce 。
- 强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字:
let equal_to_x = move |z| z == x;//x 被移动进了闭包
迭代器
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
- 迭代器是 惰性的(lazy),这意味着直到调用方法消费迭代器之前它都不会有效果;
- next 是 Iterator 实现者被要求定义的唯一方法;
- 每一个 next 调用都会从迭代器中消费一个项,这些调用 next 方法的方法被称为消费适配器
迭代器适配器
将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
如:map,但必须调用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]);
使用for写法和使用迭代器写法
// 使用for
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
// 使用闭包迭代器
contents.lines().filter(|line| line.contains(query)).collect()