Capturing the Environment with Closures

Mutable? Immutable?

Problem Statement

After reading somthing about closure in "the book" of rust, I tried to create a counter with closure, the code is here, and the build of the following code will fail:

fn main() {
    let mut x = 0;
    let counter = || {
        x += 1; // IDE: cannot borrow `counter` as mutable, as it is not declared as mutable
        x
    };
    println!("{}", counter()); // IDE: cannot borrow as mutable
}

with an error:

$ cargo run
   Compiling counter v0.1.0 (file:///projects/counter)
error[E0596]: cannot borrow `counter` as mutable, as it is not declared as mutable
 --> src/main.rs:7:20
  |
3 |     let counter = || {
  |         ------- help: consider changing this to be mutable: `mut counter`
4 |         x += 1;
  |         - calling `counter` requires mutable binding due to mutable borrow of `x`
...
7 |     println!("{}", counter());
  |                    ^^^^^^^ cannot borrow as mutable

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

Resolution

When a variable in Rust is immutable, once a value is bound to a name, you can’t change that value. Since Rust’s closures are anonymous functions you can save in a variable or pass as arguments to other functions, it is still a variable, so maybe it should follow the rules of mutability.

OK, let me find some references.

As "the book" says:

Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing mutably, and borrowing immutably. These are encoded in the three Fn traits as follows:

  • FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.

  • FnMut can change the environment because it mutably borrows values.

  • Fn borrows values from the environment immutably.

So... In my code, the value x is borrowed, but by default, the closure is immutable. I should make the closure FnMut to change the environment.

Now, let's solve the problem!

I simply add a mut to the defination of counter, besides, some more lines call of counter is added for test:

fn main() {
    let mut x = 0;
    let mut counter = || {
        x += 1;
        x
    };
    println!("{}", counter());
    println!("{}", counter());
    println!("{}", counter());
    println!("{}", counter());
    println!("{}", counter());
}

and run it:

$ cargo run
   Compiling counter v0.1.0 (file:///projects/counter)
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/counter`
1
2
3
4
5

Amazing! The program runs as wished, and the problem has been resolved.

Conclusion

As a Rust newbie, the mutability may be confused for me.

To take advantage of the safety and easy concurrency that Rust offers, maybe I should do more coding to be familiar with the feature provided by the Rust language.

Reference

Last updated