闭包 Closures
闭包是可以作为参数传递的、比较短的、可以捕获上下文环境(变量)的一段代码。
闭包的自动捕获机制
- 通过三种方式捕获变量:不可变借用、可变借用和获取所有权。
- 借用优先:若闭包函数体内的代码,只需借用即可实现功能,那就不会移动变量(转移所有权)
闭包借用
- 借用行为是发生在闭包声明之后、执行完毕之前,也就是声明后即被借用,在执行完毕之前都无法修改被捕获的值。
- 若闭包函数体内无需可变借用,则自动捕获不可变借用
- 若被捕获类型实现了copy trait,则隐式复制后再使用副本的引用(仅捕获不可变借用时)
闭包转移所有权
使用move关键字可强制转移所有权
捕获实现了copy trait的类型时,闭包捕获的是其副本(即自己复制一份拿去用,不影响原变量的使用)
闭包分类
上文提过闭包的三种捕获方式,据此将闭包分为三类,准确说是三种拥有以下不同trait组合实现的闭包:
Fn:表示捕获方式为通过引用(&T)的闭包。既不将捕获的值移出闭包体,也不修改捕获值的闭包。不从环境中捕获任何值的闭包也属于此类。这类闭包无论调用多少次都不会改变环境,常用于多次并发调用闭包的场景。FnMut:表示捕获方式为通过可变引用(&mut T)的闭包。它不会将捕获的值移出闭包体,也可能会修改捕获的值。不过这类闭包也可以被调用多次。FnOnce:表示捕获方式为通过值(T)的闭包,也就是通过所有权转移的方式。- 之所以有
FnOnce这个名字,是因为闭包在消耗掉变量的所有权之后,就没办法再次被调用了。
- 之所以有
rust
// 该函数将闭包作为参数并调用它。
fn apply<F>(f: F) where
// 闭包没有输入值和返回值。
F: FnOnce() {
// ^ 试一试:将 `FnOnce` 换成 `Fn` 或 `FnMut`。
f();
}
// 输入闭包,返回一个 `i32` 整型的函数。
fn apply_to_3<F>(f: F) -> i32 where
// 闭包处理一个 `i32` 整型并返回一个 `i32` 整型。
F: Fn(i32) -> i32 {
f(3)
}
fn main() {
use std::mem;
let greeting = "hello";
// 不可复制的类型。
// `to_owned` 从借用的数据创建有所有权的数据。
let mut farewell = "goodbye".to_owned();
// 捕获 2 个变量:通过引用捕获 `greeting`,通过值捕获 `farewell`。
let diary = || {
// `greeting` 通过引用捕获,故需要闭包是 `Fn`。
println!("I said {}.", greeting);
// 下文改变了 `farewell` ,因而要求闭包通过可变引用来捕获它。
// 现在需要 `FnMut`。
farewell.push_str("!!!");
println!("Then I screamed {}.", farewell);
println!("Now I can sleep. zzzzz");
// 手动调用 drop 又要求闭包通过值(所有权转移)获取 `farewell`。
// 现在需要 `FnOnce`。
mem::drop(farewell);
};
// 以闭包作为参数,调用函数 `apply`。
apply(diary);
// 闭包 `double` 满足 `apply_to_3` 的 trait 约束。
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double));
}