rustreferenceclosures

Is an FnMut argument a reference?


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.


Solution

  • 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 implements FnMut, &mut F implements FnMut, 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.)