在 Rust 中,闭包(closure)被设计为一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值。
闭包与其捕获环境的交互方式,决定了它会实现以下三类闭包特征中的哪一类:FnOnce、FnMut 和 Fn。这些特性规定了闭包是消耗(获取所有权)、修改(可变借用)还是仅读取(不可变借用)其环境中的值。
闭包将在定义时根据函数体中对捕获值的操作来决定使用哪种交互方式。
FnOnce 适用于只能被调用一次的闭包。所有闭包至少都实现了这个特征,因为所有闭包都能被调用。一个会将捕获的值从闭包体中移出的闭包只会实现 FnOnce,而不会实现其他 Fn 相关的特征,因为它只能被调用一次。
let mut s = "The string".to_string();
let closure = || { s.push_str(" was processed."); s };
let mut s = closure();
let _ = closure(); // 这一行编译时会报错
// use of moved value: `closure`
闭包 closure 捕获了环境中的 s,并对其进行了修改操作。所以 closure 不可能是 Fn,只可能是 FnOnce 或 FnMut。再往后看,闭包将它捕获的 s 又返回了,也就是将捕获的值从闭包体中移出,所以 closure 不是 FnMut,只能是 FnOnce。而 FnOnce 闭包只能被执行一次,所以即便后面重新绑定了一个同名的变量 s,再次调用闭包还是会编译失败。因为 FnOnce 闭包自身的所有权已经被消耗。
FnMut 适用于不会将捕获的值移出闭包体,但可能会修改捕获值的闭包。这类闭包可以被调用多次。
Fn 适用于既不将捕获的值移出闭包体,也不修改捕获值的闭包,同时也包括不从环境中捕获任何值的闭包。这类闭包可以被多次调用而不会改变其环境。
如果一个闭包实现了 FnMut 特征,那么它必定也实现了 FnOnce;如果一个闭包实现了 Fn 特征,那么它必定也实现了 FnMut。
这也就意味着,如果一个函数的泛型参数有 FnOnce 的特征约束,那么一个 FnMut 和 Fn 闭包也可以作为参数传入。同理,Fn 闭包也可以传入有 FnMut 特征约束的参数。
即使闭包体不严格需要所有权(比如 FnMut 和 Fn 闭包),如果希望强制闭包获取它在环境中所使用的值的所有权,可以在参数列表前使用 move 关键字。