Search code examples
rustclosures

Trying to understand mutable closures in Rust


I am a newby Rustacean (completing the rustling course, and so far enjoying it a lot). I am having trouble with some examples of closures in the Rust book. In particular, this one:

fn main() {     

    let mut list = vec![1, 2, 3];     

    let mut borrows_mutably = || list.push(7);      

    borrows_mutably();     
} 

The fact that we need to declare the closure as mut is difficult for me to understand. And it's really needed, because if you don't declare it as mut, the compiler complains:

calling `borrows_mutably` requires mutable binding due to mutable borrow of `list`
   |         |
   |         help: consider changing this to be mutable: `mut borrows_mutably`
   |
   |     borrows_mutably();
   |     ^^^^ cannot borrow as mutable

I have read many posts here and there and my understanding was that the closure was required to be defined as mut because it's mutating its environment. I don't know if this intuition is correct or not...

Anyway, playing with another closures I found an example that broke my understanding. This one:

fn main() {
    let mut num_ops = 0;
    let func = |r: &i32| {
        num_ops += 1;
        r.abs()
    };
    let mut list = [-3, 1, 5];
    list.sort_by_key(func);
    println!("{:?}, {}", list, num_ops);
}

I have a closure that is used for sorting a list of integers by their absolute value and by counting how many operations took place. Clearly, this closure is mutating its environment as well. But I don't need to declare it as mut in this case because, it seems, instead of calling the closure in my code, I am passing it to another function (sort_by_key) which, in turn, will call it. Why this behaviour?

I find it super confusing. In fact, if instead of passing the function to sort_by_key I call it directly, I get the same error (cannot borrow func as mutable...).


Solution

  • I have read many posts here and there and my understanding was that the closure was requiered to be defined as mut because it's mutating its environment. I don't know if this intuition is correct or not...

    Yes, this is correct. Under the hood, the closure value captures a mutable reference to list. In other words, the borrows_mutably value contains a &mut Vec<_> and it is with this reference that Vec::push is invoked.

    So why does borrows_mutably need to be declared mut? First, note that accessing a mutable reference through a shared reference does not allow mutable access. This is why the invocation signature for FnMut closures takes &mut self -- if it took &self then the &mut would be behind a &, and & &mut behaves the same as & &. Because the FnMut invocation must take &mut self, borrows_mutably needs to be declared mut.

    But I don't need to declare it as mut in this case because, it seems, instead of calling the closure in my code, I am passing it to another function (sort_by_key) which, in turn, will call it.

    Mutability is a property of the binding, not of the value. If you take an immutable binding and assign it to a mutable binding, you can mutate it.

    For example, this fails to compile:

    let foo = vec![1];
    foo.push(2);
    

    But this does compile!

    let foo = vec![1];
    let mut foo2 = foo;
    foo2.push(2);
    

    We simply moved the value in foo into a different, mutable binding. Now we can mutate it.

    The same thing is happening with sort_by_key, except it's obfuscated by the function call. When you pass the closure to this function, you are giving it away. The sort_by_key function receives this argument in a mutable binding, where it can now be invoked:

    pub fn sort_by_key<K, F>(&mut self, mut f: F)
    //                                  ^^^
    

    Note that the mutability of f is not part of the function signature, which is why it doesn't appear in the documentation. Mutability of arguments is a detail private to the function -- it's not the caller's business whether the function plans to mutate any of the values it was given.

    To clarify, an FnMut closure value can be stored in an immutable binding, but cannot be invoked from there. It needs to be moved into a mutable binding to be invoked. The line list.sort_by_key(func); is what does this in your code, it's just not immediately apparent.