A variable modified by two closures

1.3k views Asked by At

Consider the following (contrived) way to increment x by 9.

fn main() {
  let mut x = 0;
  let mut f = || {
    x += 4;
  };
  let _g = || {
    f();
    x += 5;
  };
}
error[E0499]: cannot borrow `x` as mutable more than once at a time
 --> x.rs:6:12
  |
3 |   let mut f = || {
  |               -- first mutable borrow occurs here
4 |     x += 4;
  |     - first borrow occurs due to use of `x` in closure
5 |   };
6 |   let _g = || {
  |            ^^ second mutable borrow occurs here
7 |     f();
  |     - first borrow later captured here by closure
8 |     x += 5;
  |     - second borrow occurs due to use of `x` in closure

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.

So, it does not work. How to make an algorithm like the above that would modify a variable in a closure and call another closure from it that also modifies the variable?

In traditional languages it's easy. What to do in Rust?

3

There are 3 answers

0
Alexey S. Larionov On BEST ANSWER

By design, closures have to enclose external objects that are used in it upon creation, in our case closures have to borrow the external x object. So as compiler explained to you, upon creation of the closure f it mutably borrows x, and you can't borrow it once again when you create closure g.

In order to compile it you can't enclose any external object that you want to change. Instead you can directly pass the objects as a closure's argument (arguably it's even more readable). This way you describe that a closure accepts some object of some type, but you don't yet use/pass any actual object. This object will be borrowed only when you call the closure.

fn main() {
  let mut x = 0;
  let f = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
    *local_x += 4;
  };
  let _g = |local_x: &mut i32| { // we don't enclose `x`, so no borrow yet
    f(local_x);
    *local_x += 5;
  };
  _g(&mut x); // finally we borrow `x`, and this borrow will later move to `f`, 
              // so no simultaneous borrowing.
  println!("{}", x); // 9
}
0
user4815162342 On

The accepted answer is the most idiomatic approach, but there is also an alternative that is useful in situations when the additional argument doesn't work, for example when you need to pass the closure to third-party code that will call it without arguments. In that case you can use a Cell, a form of interior mutability:

use std::cell::Cell;

fn main() {
    let x = Cell::new(0);
    let f = || {
        x.set(x.get() + 4);
    };
    let g = || {
        f();
        x.set(x.get() + 5);
    };
    f();
    g();
    assert_eq!(x.get(), 13);
}
0
Masklinn On

To expand on Alex Larionov's answer: you should think of a closure as a callable structure, anything you capture is set as a field of the structure, then implicitly dereferenced inside the function body. The way those fields are used is also what determines whether the closure is Fn, FnMut or FnOnce, basically whether the method would take &self, &mut self or self if it were written longhand.

Here

fn main() {
  let mut x = 0;
  let mut f = || {
    x += 4;
  };
  // ...
}

essentially translates to:

struct F<'a> { x: &'a mut u32 }
impl F<'_> {
    fn call(&mut self) {
        *self.x += 4
    }
}

fn main() {
    let mut x = 0;
    let mut f = F { x: &mut x };
    // ...
}

from this, you can see that as soon as f is created, x is mutably borrowed, with all that implies.

And with this partial desugaring, we can see essentially the same error: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e129f19f25dc61de8a6f42cdca1f67b5

If we use the relevant unstable features on nightly we can get just a bit closer. That is pretty much what rustc does for us under the hood.