lncr
lncr

Reputation: 886

Closures keep ownership of local variables for longer than expected

let array = [40];
let mut var = 60;

for element in array.iter().filter(|&x| {*x < var}) {
    var += 1; // Error
}

var += 1; // Fine again

To me this code seems completely legit, as the closure should be over by the time I actually access var outside of it.

error[E0506]: cannot assign to `var` because it is borrowed
 --> src/main.rs:6:9
  |
5 |     for element in array.iter().filter(|&x| {*x < var}) {
  |                                        ---- borrow of `var` occurs here
6 |         var += 1; // Error
  |         ^^^^^^^^ assignment to borrowed `var` occurs here

Why is var still borrowed when calling var += 1 even though the scope of the closure should be already over? The result is needed to get to var += 1. While it is possible to do something like this without filter, it causes my code to be a lot less clear, so I would like to keep using it.

Upvotes: 2

Views: 84

Answers (2)

Shepmaster
Shepmaster

Reputation: 432089

the closure should be over by the time I actually access

No. Iterators are lazy. That means that the order of operations here is:

  1. We call next on the combined iterator.
  2. This calls next on the iterator from the array. Repeat this step until the condition is passed.
  3. Run the body of the loop with the value.
  4. Repeat the whole process.

You are capturing val inside the filter's closure. You also try to modify it in the loop. This would mean that there has to be a mutable reference and an immutable one at the same time, which is disallowed.

You could use Cell to have interior mutability:

use std::cell::Cell;

fn main() {
    let array = [40];
    let var = Cell::new(60);

    for element in array.iter().filter(|&&x| x < var.get()) {
        var.set(var.get() + 1);
    }

    let mut var = var.get();
    var += 1;
}

Upvotes: 4

ljedrz
ljedrz

Reputation: 22273

Shepmaster's answer is correct (I didn't know iterators were that lazy), but if you don't want the var check (i.e. the filter condition) to change during the loop, you can use a move closure:

filter(move |&x| *x < var)

Since i32 implements Copy, the value of var will just be copied for the purposes of the closure.

Upvotes: 2

Related Questions