Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does moving a value into a closure still have the error message "cannot borrow immutable local variable as mutable"?

Tags:

rust

In the code below, I explicitly force the name from the main function to be moved into the closure, and everything works just fine:

fn main() {
    let name = String::from("Alice");

    let welcome = || {
        let mut name = name;
        name += " and Bob";
        println!("Welcome, {}", name);
    };

    welcome();
}

I would have thought that adding a move to the beginning of the closure would accomplish the same thing, and result in the value being moved and the creation of a FnOnce:

fn main() {
    let name = String::from("Alice");

    let welcome = move || {
        name += " and Bob";
        println!("Welcome, {}", name);
    };

    welcome();
}

Instead, however, I get the error message:

error[E0596]: cannot borrow immutable local variable `welcome` as mutable
 --> main.rs:9:5
  |
4 |     let welcome = move || {
  |         ------- help: make this binding mutable: `mut welcome`
...
9 |     welcome();
  |     ^^^^^^^ cannot borrow mutably

error[E0596]: cannot borrow captured outer variable in an `FnMut` closure as mutable
 --> main.rs:5:9
  |
5 |         name += " and Bob";
  |         ^^^^

What's the correct way to think about move on a closure in this case?

like image 941
Michael Snoyman Avatar asked Oct 28 '18 08:10

Michael Snoyman


1 Answers

I would have thought that adding a move to the beginning of the closure would accomplish the same thing, …

It kind of does the same thing. You just forgot to declare name and welcome as mutable. This code works fine:

fn main() {
    let mut name = String::from("Alice");

    let mut welcome = move || {
        name += " and Bob";
        println!("Welcome, {}", name);
    };

    welcome();
}

Both versions of the closure result in name being moved into the closure. In the first version, this is implicitly caused by consuming name inside the closure. The second version does not consume name, but uses the move keyword to force the move.

… and result in the value being moved and the creation of a FnOnce.

Moving a value into a closure does not make it FnOnce. If a closure consumes a captured value, it becomes FnOnce, since it obviously can do this only once. Thus, the first version of the closure is FnOnce, since it consumes name. The clousre above is FnMut, and can be called multiple times. Calling it twice results in the output

Welcome, Alice and Bob
Welcome, Alice and Bob and Bob

(I used the function trait names somewhat sloppily above. In fact, every closure implements FnOnce, since every closure can be called at least once. Some closures can be called multiple times, so they are FnMut in addition. And some closures that can be called multiple times don't alter their captured state, so they are Fn in addition to the other two traits.)

like image 55
Sven Marnach Avatar answered Sep 19 '22 23:09

Sven Marnach