Search code examples
rustclosuresmutablemutability

When should I make a closure mut?


Let's say I have this code:

let mut s = "hi".to_string();
let c = || s.push_str(" yo");
c();

It doesn't compile and generates this error:

error[E0596]: cannot borrow `c` as mutable, as it is not declared as mutable                 
   --> src\test.rs:120:9
    |
119 |         let c = || s.push_str(" yo");
    |             -      - calling `c` requires mutable binding due to mutable borrow of `s`  
    |             |
    |             help: consider changing this to be mutable: `mut c`
120 |         c();
    |         ^ cannot borrow as mutable

For more information about this error, try `rustc --explain E0596`.
error: could not compile `test` due to previous error

Why c(); cannot borrow as mutable? It cannot borrow what as mutable? c? In order to make it work, I have to change it to:

let mut s = "hi".to_string();
let mut c = || s.push_str(" yo");
c();

But here I'm just defining a closure c. I'll not modify it, like c = || x;. Why must define it as let mut c?


Solution

  • But here I'm just defining a closure c. I'll not modify it, like c = || x;. Why must define it as let mut c?

    Because a closure is fundamentally a structure, each captured item being a member of that structure.

    So your code is roughly equivalent to this:

    struct S<'a>(&'a mut String);
    impl S<'_> {
        fn call(&mut self) {
            self.0.push_str("yo");
        }
    }
    fn main() {
        let mut s = "hi".to_string();
        let c = S(&mut s);
        c.call();
    }
    

    And that fails to compile with more or less the same error:

    error[E0596]: cannot borrow `c` as mutable, as it is not declared as mutable
      --> src/main.rs:14:5
       |
    13 |     let c = S(&mut s);
       |         - help: consider changing this to be mutable: `mut c`
    14 |     c.call();
       |     ^^^^^^^^ cannot borrow as mutable
    

    Now you might object that that's because I defined fn call(&mut self), but if you don't you get the internal part of the same error:

    error[E0596]: cannot borrow `*self.0` as mutable, as it is behind a `&` reference
     --> src/main.rs:8:9
      |
    7 |     fn call(&self) {
      |             ----- help: consider changing this to be a mutable reference: `&mut self`
    8 |         self.0.push_str("yo");
      |         ^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
    

    That is you can't modify an &mut through an &. Because if you did you could create multiple &s to the same &mut through which you could modify the pointee, and that would essentially give you multiple &mut.

    Which is not allowed by Rust's semantics:

    At any given time, you can have either one mutable reference or any number of immutable references.