You might be misunderstanding Fn traits here. There are three traits: Fn
, FnMut
and FnOnce
. Here’s some facts:
- all closures implement
FnOnce
- some closures implement
FnMut
- every closure that can be called multiple times implements
FnMut
- every closure that implements
FnMut
can be called multiple times
- some closures that implement
FnMut
also implement Fn
, others implement FnMut
but don’t implement Fn
- every closure that implements
Fn
also implements FnMut
- a closure that cannot be called multiple times does not implement
FnMut
and does not implement Fn
. It only implements FnOnce
In particular the question “why then compiler wants FnMut if Fn is enough” does not make too much sense, since a Fn
implementation always implies a FnMut
implementation.
In my code example, the line let _unrelated = unrelated
prevents the closure from implementing FnMut
, as I said, i.e.
- without that line, the closure would implement
FnMut
and FnOnce
and would not implement Fn
- it would never implement
Fn
since the captured variables a
and b
are mutated by the closure.
- with that line added, the closure implements
FnOnce
only
As I explained above,
there’s a heuristic, as @trentj mentioned too, that whenever a (non move
) closure does not captures any value by moving out of it, i.e. as long as the code inside of a closure does not move out of any captured variable, the closure will want to implement FnOnce
and also FnMut
. The general idea being that generally it the case that a closure that doesn’t move out of any of its captured variables can be called multiple times, whereas moving out of a captured variable can only happen once, hence the closure would not implement FnMut
. The line let _unrelated = unrelated
moves out of the captured unrelated
variable, since it uses this variable by value and the variable’s type does not implement Copy
.
Of course, as demonstrated by your code example and the modification in my post, it is not true that moving out of a captured variable is the only thing that can prevent a closure from being able to be called multiple times. The borrow checker can also be unhappy with the FnMut
implementation while accepting the FnOnce
implementation of a closure.