作为输入参数
虽然 Rust 通常选择在不使用类型注释的情况下动态捕获变量,但在编写函数时不允许这种歧义。当使用闭包作为输入参数时,必须使用一些 trait
来注释闭包的完整类型,这些 trait
由闭包对捕获值的处理方式决定。以下是按照限制程度递减的顺序:
Fn
:闭包通过引用 (&T
) 使用捕获的值FnMut
:闭包通过可变引用 (&mut T
) 使用捕获的值FnOnce
:闭包通过值 (T
) 使用捕获的值
在逐个变量的基础上,编译器将以尽可能低的限制方式捕获变量。
例如,考虑一个注释为 FnOnce
的参数。这指定闭包 *可以* 通过 &T
、&mut T
或 T
进行捕获,但编译器最终将根据闭包中捕获变量的使用方式进行选择。
这是因为如果可以移动,那么任何类型的借用也应该是可能的。请注意,反之则不然。如果参数被注释为 Fn
,则不允许通过 &mut T
或 T
捕获变量。但是,允许 &T
。
在以下示例中,尝试交换 Fn
、FnMut
和 FnOnce
的用法,看看会发生什么
// A function which takes a closure as an argument and calls it. // <F> denotes that F is a "Generic type parameter" fn apply<F>(f: F) where // The closure takes no input and returns nothing. F: FnOnce() { // ^ TODO: Try changing this to `Fn` or `FnMut`. f(); } // A function which takes a closure and returns an `i32`. fn apply_to_3<F>(f: F) -> i32 where // The closure takes an `i32` and returns an `i32`. F: Fn(i32) -> i32 { f(3) } fn main() { use std::mem; let greeting = "hello"; // A non-copy type. // `to_owned` creates owned data from borrowed one let mut farewell = "goodbye".to_owned(); // Capture 2 variables: `greeting` by reference and // `farewell` by value. let diary = || { // `greeting` is by reference: requires `Fn`. println!("I said {}.", greeting); // Mutation forces `farewell` to be captured by // mutable reference. Now requires `FnMut`. farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); println!("Now I can sleep. zzzzz"); // Manually calling drop forces `farewell` to // be captured by value. Now requires `FnOnce`. mem::drop(farewell); }; // Call the function which applies the closure. apply(diary); // `double` satisfies `apply_to_3`'s trait bound let double = |x| 2 * x; println!("3 doubled: {}", apply_to_3(double)); }