笔者之前一直不理解 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
是最小、最核心的那个集合。