笔者之前一直不理解 Rust 中关于闭包的 Fn/FnMut/FnOnce 这 3 个 trait 的包含关系。通过一段时间的学习和分析,终于找到了我思维上的一个错误点 ,特此梳理此文,方便日后查询。

我之前的理解是这样的:

Fn 只需要引用,所以要求是最容易满足的。FnMut 需要的是可变引用,所以能满足 FnMut,一定能满足 Fn。FnOnce 需要的是所有权,那都有所有权了 ,&mut 和 & 肯定就不在话下了。所以满足 FnOnce 的,一定是 Fn 和 FnMut。满足 FnMut 的,不一定是 FnOnce,但是一定是 Fn。

我的错误点在于:在闭包实现者的角度想"我拥有什么权限"。

正确的思路应该是:站在函数调用者的角度想"我得到了什么承诺"。这才是 Trait 设计的本质,即是能力的声明,更是限制的承诺。


核心关键:承诺越强,限制越多,类型越“小”

Fn, FnMut, FnOnce 这三个 Trait,本质上是闭包对调用者做出的三种不同强度的承诺

  1. Fn 的承诺 (最强的承诺)
    • 承诺内容:调用我,只需要对我进行不可变借用 (&self)。你甚至可以同时在多个线程里调用我。我保证不会改变任何东西,也不会消耗掉自己。
    • 对闭包的限制:为了兑现这个最强的承诺,闭包自身受到的限制也最大。它只能不可变地借用环境中的变量。
    • 调用者的自由:调用者获得了最大的自由,可以随心所欲地、不限次数地调用这个闭包。
  2. FnMut 的承诺 (中等的承诺)
    • 承诺内容:调用我,需要对我进行可变借用 (&mut self)。这意味着你不能同时调用我,但可以一个接一个地调用。我可能会改变我内部的状态。
    • 对闭包的限制:限制有所放宽。闭包可以可变地借用环境变量。
    • 调用者的自由:调用者的自由受到了一些限制,不能并发调用了。
  3. FnOnce 的承诺 (最弱的承诺)
    • 承诺内容:你只能调用我一次 (self)。调用之后,我就会被消耗掉,不复存在。
    • 对闭包的限制:对闭包自身的限制最小。它可以随心所欲,甚至可以拿走环境变量的所有权
    • 调用者的自由:调用者只拥有一次调用的机会,自由度最低。

将之前的逻辑反过来思考

用"承诺"的视角:

  • 错误想法FnOnce 有所有权,最厉害,所以它包含了 FnMutFn
  • 正确的逻辑:一个闭包如果能做出 Fn 的承诺(最强承诺),那么它自然也能满足 FnMut(中等承诺)和 FnOnce(最弱承诺)的要求。

这就像一个人的信用评级:

  • 一个能被评为 AAA 级信用 (Fn) 的人,向他借钱(调用他)风险极低,可以随时借。他自然也满足 AA 级 (FnMut)A 级 (FnOnce) 的标准。
  • 一个被评为 AA 级信用 (FnMut) 的人,满足不了 AAA 级的苛刻标准,但他肯定满足 A 级的基本标准。
  • 一个只有 A 级信用 (FnOnce) 的人,意味着和他交易有风险,只能“一次性买卖”,他肯定满足不了 AA 级和 AAA 级的要求。

所以,这个关系是:

  • 凡是 Fn,必然是 FnMutFnOnce
  • 凡是 FnMut,必然是 FnOnce,但不一定是 Fn
  • FnOnce 最为宽泛,它不承诺自己是 FnMutFn

代码验证

我们用一个具体的例子来印证这个理论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 一个函数,它要求一个“AAA信用”的闭包
fn call_repeatedly<F: Fn()>(closure: F) {
println!("--- Calling an Fn closure ---");
closure();
closure();
}

// 一个函数,它要求一个“AA信用”的闭包
fn call_mutably<F: FnMut()>(mut closure: F) {
println!("--- Calling an FnMut closure ---");
closure();
closure();
}

// 一个函数,它只要求“A信用”的闭包
fn call_once<F: FnOnce()>(closure: F) {
println!("--- Calling an FnOnce closure ---");
closure();
}

fn main() {
let mut my_string = String::from("Hello");
let owned_string = String::from("World");

// 1. 这是一个 Fn 闭包,因为它只对 my_string 进行了不可变借用。
// 它做出了最强的承诺。
let closure_fn = || {
println!("Fn says: {}", my_string);
};

// 2. 这是一个 FnMut 闭包,因为它对 my_string 进行了可变借用。
// 它只能做出中等承诺。
let mut closure_fn_mut = || {
my_string.push_str("!");
println!("FnMut says: {}", my_string);
};

// 3. 这是一个 FnOnce 闭包,因为它夺走了 owned_string 的所有权。
// 它只能做出最弱的承诺。
let closure_fn_once = || {
let consumed = owned_string;
println!("FnOnce says: {}", consumed);
};

// --- 开始验证 ---

// `closure_fn` (AAA级) 可以满足所有要求
call_repeatedly(closure_fn);
call_mutably(closure_fn);
call_once(closure_fn);

println!("\n");

// `closure_fn_mut` (AA级) 满足不了 AAA 级的要求
// call_repeatedly(closure_fn_mut); // 编译错误!因为它改变了环境,不满足 Fn 的要求
call_mutably(&mut closure_fn_mut);
call_once(&mut closure_fn_mut);

println!("\n");

// `closure_fn_once` (A级) 只能满足最基本的要求
// call_repeatedly(closure_fn_once); // 编译错误!
// call_mutably(closure_fn_once); // 编译错误!因为它被调用后就没了,不能调用第二次
call_once(closure_fn_once);
// call_once(closure_fn_once); // 再次调用也会编译错误,因为它已经被消耗了
}

总结

在 Rust 的 Trait 系统中,一个类型如果满足更强的承诺(Fn),它就能被用在要求较弱承诺(FnMut, FnOnce)的任何地方。这就是为什么 Fn 是最小、最核心的那个集合。