I've been trying to understand closures better. I originally wrote
fn main() {
let mut x = 0;
let f = || x += 1;
call_me(f);
f();
println!("x = {x}");
}
fn call_me<F>(f: F)
where
F: FnMut(),
{
f();
}
This didn't compile, of course. After following the compiler's suggestions, I eventually arrived at
fn main() {
let mut x = 0;
let mut f = || x += 1;
call_me(&mut f);
f();
println!("x = {x}");
}
fn call_me<F>(mut f: F)
where
F: FnMut(),
{
f();
}
So, I'm passing f
into call_me
as a mutable reference. This makes sense. However, why then is it okay that call_me
's signature doesn't mark f
as a reference? It appears to take f
by value.
You are passing it as a mutable reference but you aren't accepting it as a mutable reference. That would be f: &mut F
.
What you're declaring with mut
is that the body of call_me
can mutate f
. It is the same difference between let f = ...;
and let mut f = ...;
, except applied to an argument instead of a variable. (The arguments essentially are variables in the context of the function body.)
Note that mutability of f
is an implementation detail of the function and does not affect its signature. If you generated documentation for call_me
this detail would be omitted, because it's not relevant for the caller.
At the end of the day, your definition of call_me
is in every semantic aspect nearly (if not completely) identical to:
fn call_me<F>(f: F)
where
F: FnMut(),
{
let mut f = f;
f();
}
The mut
is required because invoking an FnMut
closure requires taking a &mut
to the closure value.
As to why this works when you pass in a mutable reference, the FnMut
documentation states:
Additionally, for any type
F
that implementsFnMut
,&mut F
implementsFnMut
, too.
You can therefore do exactly what you're doing here to avoid giving away a closure that you need to invoke later, lifetimes permitting. (You can do the same thing with other values of certain traits, like Iterator
, to avoid giving away values you need to keep. In the case of Iterator
, this is because of the blanket implementation impl<I: Iterator + ?Sized> Iterator for &mut I
. FnMut
effectively has a blanket implementation like this but because the language is defined to have it, not because it's provided by the standard library.)