笔者之前一直不理解 Rust 中关于闭包的 Fn/FnMut/FnOnce 这 3 个 trait 的包含关系。通过一段时间的学习和分析,终于找到了我思维上的一个错误点 ,特此梳理此文,方便日后查询。
我之前的理解是这样的:
Fn 只需要引用,所以要求是最容易满足的。FnMut 需要的是可变引用,所以能满足 FnMut,一定能满足 Fn。FnOnce 需要的是所有权,那都有所有权了 ,&mut 和 & 肯定就不在话下了。所以满足 FnOnce 的,一定是 Fn 和 FnMut。满足 FnMut 的,不一定是 FnOnce,但是一定是 Fn。
我的错误点在于:在闭包实现者的角度想"我拥有什么权限"。
正确的思路应该是:站在函数调用者的角度想"我得到了什么承诺"。这才是 Trait 设计的本质,即是能力的声明,更是限制的承诺。
核心关键:承诺越强,限制越多,类型越“小”
Fn
, FnMut
, FnOnce
这三个
Trait,本质上是闭包对调用者做出的三种不同强度的承诺。
Fn
的承诺 (最强的承诺)- 承诺内容:调用我,只需要对我进行不可变借用
(
&self
)。你甚至可以同时在多个线程里调用我。我保证不会改变任何东西,也不会消耗掉自己。 - 对闭包的限制:为了兑现这个最强的承诺,闭包自身受到的限制也最大。它只能不可变地借用环境中的变量。
- 调用者的自由:调用者获得了最大的自由,可以随心所欲地、不限次数地调用这个闭包。
- 承诺内容:调用我,只需要对我进行不可变借用
(
FnMut
的承诺 (中等的承诺)- 承诺内容:调用我,需要对我进行可变借用
(
&mut self
)。这意味着你不能同时调用我,但可以一个接一个地调用。我可能会改变我内部的状态。 - 对闭包的限制:限制有所放宽。闭包可以可变地借用环境变量。
- 调用者的自由:调用者的自由受到了一些限制,不能并发调用了。
- 承诺内容:调用我,需要对我进行可变借用
(
FnOnce
的承诺 (最弱的承诺)- 承诺内容:你只能调用我一次
(
self
)。调用之后,我就会被消耗掉,不复存在。 - 对闭包的限制:对闭包自身的限制最小。它可以随心所欲,甚至可以拿走环境变量的所有权。
- 调用者的自由:调用者只拥有一次调用的机会,自由度最低。
- 承诺内容:你只能调用我一次
(
将之前的逻辑反过来思考
用"承诺"的视角:
- 错误想法:
FnOnce
有所有权,最厉害,所以它包含了FnMut
和Fn
。 - 正确的逻辑:一个闭包如果能做出
Fn
的承诺(最强承诺),那么它自然也能满足FnMut
(中等承诺)和FnOnce
(最弱承诺)的要求。
这就像一个人的信用评级:
- 一个能被评为 AAA 级信用 (
Fn
) 的人,向他借钱(调用他)风险极低,可以随时借。他自然也满足 AA 级 (FnMut
) 和 A 级 (FnOnce
) 的标准。 - 一个被评为 AA 级信用 (
FnMut
) 的人,满足不了 AAA 级的苛刻标准,但他肯定满足 A 级的基本标准。 - 一个只有 A 级信用 (
FnOnce
) 的人,意味着和他交易有风险,只能“一次性买卖”,他肯定满足不了 AA 级和 AAA 级的要求。
所以,这个关系是:
- 凡是
Fn
,必然是FnMut
和FnOnce
。 - 凡是
FnMut
,必然是FnOnce
,但不一定是Fn
。 FnOnce
最为宽泛,它不承诺自己是FnMut
或Fn
。
代码验证
我们用一个具体的例子来印证这个理论。
1 | // 一个函数,它要求一个“AAA信用”的闭包 |
总结
在 Rust 的 Trait
系统中,一个类型如果满足更强的承诺(Fn
),它就能被用在要求较弱承诺(FnMut
,
FnOnce
)的任何地方。这就是为什么 Fn
是最小、最核心的那个集合。