Understanding problem with borrowing

I get an error with this code:

fn main() {
    let mut x = vec![1, 2, 3];
    let mut foo = |i: usize, v: i32| { x[i] = v; };
    for _n in 0..1 {
        foo(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

But when I write it inside of the loop, then it works:

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = |i: usize, v: i32| { x[i] = v; };
        foo(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

Why does the borrow happen when the closure is defined, and not when it is called? Looks both safe to me.
And what's even stranger: I can use the foo closure more than once, but not after the assignment, this gives also an error:

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = |i: usize, v: i32| { x[i] = v; };
        foo(0, 1);
        foo(0, 1);
        x[1] = 1;
        foo(0, 1);
    }
    println!("{:?}", x);
}

Looks also very safe to me, even if I wouldn't use different indices in the vector. For me it looks exactly like this, which compiles without error, for all the cases above:

fn foo(x: &mut Vec<i32>, i: usize, v: i32) {
    x[i] = v;
}

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        foo(&mut x, 0, 1);
        foo(&mut x, 0, 1);
        x[1] = 1;
        foo(&mut x, 0, 1);
    }
    println!("{:?}", x);
}

You posted the same code twice here :wink:

I guess your first example was supposed to be

fn main() {
    let mut x = vec![1, 2, 3];
    let mut foo = |i: usize, v: i32| { x[i] = v; };
    for _n in 0..1 {
        foo(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

Thanks, I fixed it.

Closures in Rust are essentially structs with a call method; they capture the variables from outside either by value of by reference.

In

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = |i: usize, v: i32| { x[i] = v; };
        foo(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

the closure needs to capture the variable x. It modifies x (via mutable indexing), so it captures by mutable reference. x is a Vec<i32>, so the closure is – internally – something like

struct Closure<'a> {
    x_mut: &'a mut Vec<i32>,
}

and it implements a call method that takes &mut self whose body is essentially the body { x[i] = v; }, but with x replaced by (*self.x_mut), i.e.

impl Closure<'_> {
    fn call(&mut self, i: usize, v: i32) {
        (*self.x_mut)[i] = v;
    }
}

then finally, calling foo is calling this method, and creating the closure is just initializing the field x_mut with a mutable reference to x:

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = Closure { x_mut: &mut x };
        foo.call(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

With this “desugaring” of the closure here in mind, it’s possible to explain everything the borrow checker does. The closure essentially just consists of the &mut x reference. The reason why

fn main() {
    let mut x = vec![1, 2, 3];
    let mut foo = |i: usize, v: i32| { x[i] = v; };
    for _n in 0..1 {
        foo(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

or

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = |i: usize, v: i32| { x[i] = v; };
        foo(0, 1);
        foo(0, 1);
        x[1] = 1;
        foo(0, 1);
    }
    println!("{:?}", x);
}

won’t compile is the same reason why

struct Closure<'a> {
    x_mut: &'a mut Vec<i32>,
}
impl Closure<'_> {
    fn call(&mut self, i: usize, v: i32) {
        (*self.x_mut)[i] = v;
    }
}

fn main() {
    let mut x = vec![1, 2, 3];
    let mut foo = Closure { x_mut: &mut x };
    for _n in 0..1 {
        foo.call(0, 1);
        x[1] = 1;
    }
    println!("{:?}", x);
}

or

struct Closure<'a> {
    x_mut: &'a mut Vec<i32>,
}
impl Closure<'_> {
    fn call(&mut self, i: usize, v: i32) {
        (*self.x_mut)[i] = v;
    }
}

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let mut foo = Closure { x_mut: &mut x };
        foo.call(0, 1);
        foo.call(0, 1);
        x[1] = 1;
        foo.call(0, 1);
    }
    println!("{:?}", x);
}

won’t compile; and yet again, the same why

fn main() {
    let mut x = vec![1, 2, 3];
    let x_mut = &mut x;
    for _n in 0..1 {
        x_mut[0] = 1;
        x[1] = 1;
    }
    println!("{:?}", x);
}

or

fn main() {
    let mut x = vec![1, 2, 3];
    for _n in 0..1 {
        let x_mut = &mut x;
        x_mut[0] = 1;
        x_mut[0] = 1;
        x[1] = 1;
        x_mut[0] = 1;
    }
    println!("{:?}", x);
}

won’t compile :slight_smile:

Do closures have to be this way? Probably not… and even with the &mut x being part of the closure, I can imagine a future where the Rust compile could somehow recognize that that mutable reference could implicitly get some sort of special new broken-up-into-small-pieces kind of lifetime that could make your original code example work, and that could also make the last example using x_mut directly work. But that’s wild speculation; right now, closures that mutate a captured variable contain a mutable/unique reference to that variable, and you cannot access that variable until the closure is no longer used anymore, so that that unique reference can expire.

As for your other code example with the fn foo, that’s different: here x is a function argument – you can do the same thing with a closure, too:

fn main() {
    let mut x = vec![1, 2, 3];
    let foo = |x: &mut Vec<i32>, i, v| x[i] = v;
    for _n in 0..1 {
        foo(&mut x, 0, 1);
        foo(&mut x, 0, 1);
        x[1] = 1;
        foo(&mut x, 0, 1);
    }
    println!("{:?}", x);
}

that’s actually a good workaround in general: turn those captures that you want to have the closure borrow anew with each call into function arguments instead. When the closure contains a bit more code, this can still be reasonable; of course with the example above, calling the closure is more lengthy than directly doing the indexing+assigning operation, so it’s a bit of a unrealistic/toy example.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.