作为输入参数

虽然 Rust 通常选择在不使用类型注释的情况下动态捕获变量,但在编写函数时不允许这种歧义。当使用闭包作为输入参数时,必须使用一些 trait 来注释闭包的完整类型,这些 trait 由闭包对捕获值的处理方式决定。以下是按照限制程度递减的顺序:

  • Fn:闭包通过引用 (&T) 使用捕获的值
  • FnMut:闭包通过可变引用 (&mut T) 使用捕获的值
  • FnOnce:闭包通过值 (T) 使用捕获的值

在逐个变量的基础上,编译器将以尽可能低的限制方式捕获变量。

例如,考虑一个注释为 FnOnce 的参数。这指定闭包 *可以* 通过 &T&mut TT 进行捕获,但编译器最终将根据闭包中捕获变量的使用方式进行选择。

这是因为如果可以移动,那么任何类型的借用也应该是可能的。请注意,反之则不然。如果参数被注释为 Fn,则不允许通过 &mut TT 捕获变量。但是,允许 &T

在以下示例中,尝试交换 FnFnMutFnOnce 的用法,看看会发生什么

// 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));
}

另请参阅

std::mem::dropFnFnMut泛型whereFnOnce