Borrow checker stops iterator from calling for_each inside a recursive function

Viewed 78

I am working on a minesweeper copy in Rust and WASM. At the moment, I'm finishing the logic for Mine Sweeper. Mainly, when the user clicks on an empty tile with no bombs near it, the program then searches the neighbors and reveals those tiles if they are not near any bombs.

Notice the large empty spaces on the game that opens up automatically.

image of mine sweeper

The program uses two 1D vectors to store where the bombs are and also what is the state of each tile.

Then this recursive program sets the state of a found empty tile to SpotState::Empty, then continues this with the neighbors.

/// for uncovering empty neighbors and recursively uncovering their empty neighbors
fn uncover_empty_neighbors(&mut self, col: usize, row: usize) {
    // break case for recursion
    if self.state_vec[self.get_idx(col, row)] == SpotState::Empty {
        return;
    }

    if self.get_mine_neighbor_count(col, row) == 0 {
        let idx = self.get_idx(col, row);
        self.state_vec[idx] = SpotState::Empty;
    }

    let inbound_neighbors = POSSIBLE_NEIGHBORS
        .iter()
        .map(|[x, y]| [x + col as isize, y + row as isize])
        .filter(|[x, y]| self.check_bounds(x, y))
        .for_each(|[x, y]| self.uncover_empty_neighbors(x as usize, y as usize));
}

However, I get this error:

error[E0500]: closure requires unique access to `*self` but it is already borrowed
   --> src\lib.rs:138:23
    |
137 |             .filter(|[x, y]| self.check_bounds(x, y))
    |                     -------- ---- first borrow occurs due to use of `*self` in closure
    |                     |
    |                     borrow occurs here
138 |             .for_each(|[x, y]| {
    |              -------- ^^^^^^^^ closure construction occurs here
    |              |
    |              first borrow later used by call
139 |                 self.uncover_empty_neighbors(x as usize, y as usize)
    |                 ---- second borrow occurs due to use of `*self` in closure

For more information about this error, try `rustc --explain E0500`.
error: could not compile `minesweeper_wasm` due to previous error
warning: build failed, waiting for other jobs to finish...
error: build failed

I'm confused about how to accomplish this. Is the closure unable to capture self since self is already mutably borrowed in the function uncover_empty_neighbors()? Is using self inevitably a bad move in this case and what other data structure would be useful?

Link to repo

2 Answers

You just need to try to avoid borrowing when possible. In this case making a free method taking an owned width/height is enough, considering that they are usize which is Copy:

/// for checking the bounds of a given row and col since neighbors are blindly checked.
fn check_bounds(col: &isize, row: &isize, width: usize, height: usize) -> bool {
    if col < &0 || row < &0 || col >= &(width as isize) || row >= &(height as isize) {
        return false;
    }
    true
}

/// for uncovering empty neighbors
fn uncover_empty_neighbors(&mut self, col: usize, row: usize) {
    // break case for recursion
    if self.state_vec[self.get_idx(col, row)] == SpotState::Empty {
        return;
    }

    if self.get_mine_neighbor_count(col, row) == 0 {
        let idx = self.get_idx(col, row);
        self.state_vec[idx] = SpotState::Empty;
    }
    let (width, height) = (self.width, self.height);
    let inbound_neighbors = POSSIBLE_NEIGHBORS
        .iter()
        .map(|[x, y]| [x + col as isize, y + row as isize])
        .filter(|[x, y]| Self::check_bounds(x, y, width, height))
        .for_each(|[x, y]| {
            self.uncover_empty_neighbors(x.clone() as usize, y.clone() as usize)
        });
}

Playground

What is going on? Well, you are capturing self withing the closures that the iterator use, since iterators are lazy that borrow is not release until you collect (or in your case the whole for_each is evaluated).

Yes. Remember that iterators are evaluated lazily, so the closure passed into filter needs to borrow self and keep borrowing it while for_each is executing, as for_each needs to evaluate the iterator it is called on.

You can either try to restructure the code so that either of the methods does not depend on self, or you can simply collect() into a new Vec after the filter(), so that there is no immutable borrow alive as uncover_empty_neighbors executes.

Related