Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot assign to a variable used in a closure because it is borrowed

Tags:

closures

rust

I'm trying to make some sections of my code easier to read by using small helper closures.

fn main() {
    let mut loop_index = 0;

    let get_src_index = || return loop_index % 2;
    let get_dst_index = || return (loop_index + 1) % 2;

    loop_index += 1;
}

There's a few more closures that make use of get_src_index() and get_dst_index(), but they are not important to the problem. The problem is that I can no longer modify loop_index:

error[E0506]: cannot assign to `loop_index` because it is borrowed
 --> src/main.rs:6:5
  |
4 |     let get_src_index = || return loop_index % 2;
  |                         -- borrow of `loop_index` occurs here
5 |     let get_dst_index = || return (loop_index + 1) % 2;
6 |     loop_index += 1;
  |     ^^^^^^^^^^^^^^^ assignment to borrowed `loop_index` occurs here

I don't quite understand why Rust doesn't allow incrementing the loop_index variable. Is there a more "Rusty" way of doing this?

like image 714
Niklas R Avatar asked Sep 16 '25 23:09

Niklas R


1 Answers

As the error message says, you can't change the value because it's borrowed. It's the same as if you had said:

let mut loop_index = 0;
let foo = &loop_index;
loop_index += 1;

You aren't allowed to modify a value while there is an outstanding immutable borrow. This is a fundamental concept in Rust and underpins the safety it provides.

Why does the closure have a reference to the variable? That's the entire point of closures — to capture the environment. Closures infer how to capture the variables based on the operations taking place inside, and in this case a reference is sufficient.

This is normally what you want because a reference is easy to pass around. In this case, you have numbers so there's no reason to prefer the reference. We can use the move keyword to move the number into the closure. Since numbers implement Copy, this will make a copy. Then the closure is completely separate from the existing value:

let mut loop_index = 0;

let get_src_index = move || loop_index % 2; // No need for `return`
let get_dst_index = move || (loop_index + 1) % 2;

loop_index += 1;

However, I don't think that these particular closures really buy you anything. Integer operations are generally cheap and conditionals are often less cheap, so doing an extra computation might be better.


If you needed the ability to modify the loop_index and have that change reflected inside the closures, you could use a Cell:

use std::cell::Cell;

fn main() {
    let mut loop_index = Cell::new(0);

    let get_src_index = || loop_index.get() % 2;
    let get_dst_index = || (loop_index.get() + 1) % 2;

    loop_index.set(loop_index.get() + 1);
}

Even better, in many cases you don't need to maintain your own loop index anyway. Iterator::enumerate takes care of it for you.

like image 93
Shepmaster Avatar answered Sep 19 '25 13:09

Shepmaster