借用检查器阻止迭代器在递归函数内调用 for_each

Borrow checker stops iterator from calling for_each inside a recursive function

我正在用 Rust 和 WASM 制作扫雷副本。目前,我正在完成 Mine Sweeper 的逻辑。主要是,当用户点击一个附近没有炸弹的空方块时,程序会搜索邻居并显示那些附近没有炸弹的方块。

请注意自动打开的游戏中的大片空白区域。

该程序使用两个一维向量来存储炸弹的位置以及每个图块的状态。

然后这个递归程序将找到的空瓦片的状态设置为 SpotState::Empty,然后与邻居继续此操作。

/// 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));
}

但是,我得到这个错误:

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

我对如何实现这一点感到困惑。闭包是否无法捕获 self 因为 self 已经在函数 uncover_empty_neighbors() 中可变地借用了?在这种情况下使用 self 是否不可避免地是一个糟糕的举动,还有什么其他数据结构会有用?

Link to repo

你只需要尽可能避免借钱。在这种情况下,考虑到它们是 usize,即 Copy:

,制作一个采用拥有的 width/height 的免费方法就足够了
/// 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

这是怎么回事?好吧,您正在使用迭代器使用的闭包捕获 self,因为迭代器是惰性的,因此 borrow 在您收集之前不会释放(或者在您的情况下整个 for_each 被评估)。

是的。请记住,迭代器是延迟计算的,因此传递给 filter 的闭包需要借用 self 并且 for_each 执行时继续借用它 ,因为 for_each 需要评估调用它的迭代器。

您可以尝试重组代码,使任何一种方法都不依赖于 self,或者您可以简单地 collect()Vec 之后的新 Vec 17=],因此在 uncover_empty_neighbors 执行时没有不可变借用。