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...
).
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.