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 errorResolution
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
Fntraits as follows:
FnOnceconsumes 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. TheOncepart 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.
FnMutcan change the environment because it mutably borrows values.
Fnborrows 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
5Amazing! 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