作为输入参数
Rust 通常能自动选择如何捕获变量,无需类型标注。但在编写函数时,这种模糊性是不允许的。当将闭包作为输入参数时,必须使用特定的 trait 来注解闭包的完整类型。这些 trait 由闭包对捕获值的处理方式决定。按限制程度从高到低排列如下:
Fn:闭包通过引用使用捕获的值(&T)FnMut:闭包通过可变引用使用捕获的值(&mut T)FnOnce:闭包通过值使用捕获的值(T)
编译器会以尽可能最少限制的方式逐个捕获变量。
例如,考虑一个注解为 FnOnce 的参数。这表示闭包可能通过 &T、&mut T 或 T 进行捕获,但编译器最终会根据捕获变量在闭包中的使用方式来决定。
这是因为如果可以移动,那么任何类型的借用也应该是可能的。注意反过来并不成立。如果参数被注解为 Fn,那么通过 &mut T 或 T 捕获变量是不允许的。但 &T 是允许的。
在下面的例子中,尝试交换 Fn、FnMut 和 FnOnce 的用法,看看会发生什么:
// 这个函数接受一个闭包作为参数并调用它
// <F> 表示 F 是一个"泛型类型参数"
fn apply<F>(f: F) where
// 这个闭包不接受输入也不返回任何值
F: FnOnce() {
// ^ TODO:试着将其改为 `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();
// 捕获两个变量:通过引用捕获 `greeting`,
// 通过值捕获 `farewell`
let diary = || {
// `greeting` 是通过引用捕获的:需要 `Fn`
println!("我说{}。", greeting);
// 修改强制 `farewell` 通过可变引用捕获
// 现在需要 `FnMut`
farewell.push_str("!!!");
println!("然后我喊{}。", farewell);
println!("现在我可以睡觉了。呼呼");
// 手动调用 drop 强制 `farewell` 通过值捕获
// 现在需要 `FnOnce`
mem::drop(farewell);
};
// 调用应用闭包的函数
apply(diary);
// `double` 满足 `apply_to_3` 的 trait 约束
let double = |x| 2 * x;
println!("3 的两倍是:{}", apply_to_3(double));
}