I have just (again) manually rewritten the section about closure trait bounds, as below.
This topic might be not too important in real life, but for me it was difficult to understand, at least I was mostly confused by the explanation in the official book. AI typically tries to replicate the explanation style of the official book -- and when I ask for a check or improvement, it has the strong tendency to delete my own text. My personal feeling is that my explanation strategy is quite good, I might do some fine-tuning (delete the diagram?), but I think I will keep it. What made it difficult for me to understand is the fact that the hierarchy of the traits practically reverses from the definition of a closure to their use as function parameters. What do you think?
12.2 Closure Traits: FnOnce
, FnMut
, and Fn
How a closure interacts with its captured environment determines which of the three closure traits it implements: FnOnce
, FnMut
, and Fn
. These traits dictate whether the closure consumes (takes ownership of), mutates (mutably borrows), or only reads (immutably borrows) its environment.
Implementation Hierarchy:
The traits form an implementation hierarchy based on the closure's capabilities:
- Every closure implements
FnOnce
, as any closure can conceptually be called at least once, potentially consuming its environment in the process. - Closures that do not consume their environment (they only borrow it mutably or immutably) also implement
FnMut
, allowing them to be called multiple times while potentially changing their environment. - Closures that only require immutable access to their environment (or capture nothing) also implement
Fn
, allowing them to be called multiple times without changing their environment.
This means Fn
implies FnMut
, and FnMut
implies FnOnce
. A closure implementing Fn
can be used anywhere an FnMut
or FnOnce
is expected; an FnMut
can be used where an FnOnce
is expected.
+--------------------+ +---------------------+ +--------------------+
| Implements Fn | --> | Implements FnMut | --> | Implements FnOnce |
| (Immutable Borrow) | | (& Immutable Borrow)| | (& Mutable Borrow) |
| (Callable Many) | | (Callable Many) | | (& Consuming) |
+--------------------+ +---------------------+ | (Callable Once) |
+--------------------+
(Arrow indicates "also implements")
The compiler automatically determines the most specific trait(s) (Fn
, FnMut
, or just FnOnce
) that a closure implements based on how its body interacts with captured variables.
Usage as Trait Bounds:
Functions accepting closures use these traits as bounds in their generic signatures (e.g., <F: FnMut(i32) -> i32>
). When used this way, the hierarchy relates to the permissiveness of the bound – what kinds of closures the function accepts:
F: FnOnce(...)
: This is the most permissive (least restrictive) bound. It accepts any closure matching the signature (Fn
,FnMut
, orFnOnce
), as it only requires the closure to be callable at least once.F: FnMut(...)
: This bound is more restrictive. It accepts closures implementingFnMut
orFn
(sinceFn
impliesFnMut
), requiring the closure to be callable multiple times, potentially mutating its environment. It rejects closures that only implementFnOnce
(i.e., consuming closures).F: Fn(...)
: This is the most restrictive bound. It only accepts closures implementingFn
, requiring that the closure can be called multiple times without mutation. It rejects closures that only implementFnMut
orFnOnce
.
Choosing the right bound depends on how the function intends to use the closure: call once (FnOnce
), call multiple times with mutation (FnMut
), or call multiple times without mutation (Fn
).