Невозможно одолжить Vec внутри закрытия

У меня есть вектор Units. Я пытаюсь создать переменную Vec<Vec<Unit>> с именем world. В процессе создания плиток мне нужно знать, какие плитки являются соседними, о которых он знает.

У меня есть функция, которая возвращает вектор точек ((usize, usize)), смежных с другой точкой, и я преобразовываю его в итератор, сопоставляю его и получаю фактическую единицу, связанную с этой позицией в world, если она там есть, или текущая строка, которая еще не была зафиксирована в world.

Мне нужно получить доступ к этой строке внутри замыкания, которое принимает map, но мне также нужно получить к ней доступ позже.

fn adjacent((x, y): (usize, usize)) -> Vec<(usize, usize)> {
    vec![
        (x+1, y),
        (x-1, y),
        (x, y+1),
        (x, y-1),
    ]
}

struct Unit {
    pub tiles: Vec<Tile>,
}

#[derive(Copy, Clone)]
enum Tile {
    Floor, Wall, Empty
}

fn main() {
    let heightmap = vec![
        vec![3, 3, 3, 3, 3, 3],
        vec![3, 1, 1, 1, 1, 3],
        vec![3, 1, 1, 1, 1, 3],
        vec![3, 1, 1, 1, 1, 3],
        vec![3, 1, 1, 1, 1, 3],
        vec![3, 1, 1, 1, 1, 3],
        vec![3, 3, 3, 3, 3, 3],
    ];
    let (sx, sy) = (5, 5);
    let mut world: Vec<Vec<Unit>> = vec![];
    for y in 0..sy {
        let mut line: Vec<Unit> = vec![];
        for x in 0..sx {
            let mut tiles: Vec<Tile> = vec![];
            let height = heightmap[y][x];
            let adj = adjacent((x, y))
                .iter()
                .map(|&(x, y)| {
                    let list = if y > world.len() {
                        vec![]
                    } else if y == world.len() {
                        line
                    } else {
                        world[y]
                    };

                    if x >= list.len() {
                        Tile::Empty
                    } else {
                        if height as usize >= list[x].tiles.len() {
                            Tile::Empty
                        } else {
                            list[x].tiles[height as usize]
                        }
                    }
                })
                .collect::<Vec<_>>();

            for z in 0..(height as isize - 1) {
                tiles.push(Tile::Wall);
            }
            line.push(Unit {
                tiles: tiles,
            });
        }
        world.push(line);
    }
}

Вот Игровая площадка Rust.

Я бы предпочел использовать unwrap_or в сочетании с get и так далее, но это приводит к другим ошибкам с временными значениями, и в любом случае это было бы сложнее, поэтому приведенный выше код, хотя, вероятно, и не оптимален, настолько прост, насколько я могу понять, пока все еще дублирует ошибку. Я обнаружил, что многие ошибки, которые я получаю с Rust, связаны с тем, что то, что я делаю, - не лучший способ сделать это, поэтому, если есть более идиоматический способ, я, безусловно, хотел бы знать.

Я пытался сделать Unit клонируемым (получив Clone и Copy), но Rust по какой-то причине не позволяет мне это сделать, хотя все, из чего он состоит, — это векторы, которые можно клонировать, если их элементы клонируются.


person Christopher Dumas    schedule 07.07.2017    source источник


Ответы (1)


Вот меньший пример вашей проблемы:

fn main() {
    let mut line = vec![1];
    let something_to_iterate_over = vec![true, false, true];

    for _ in 0..2 {
        let _dummy: Vec<_> = something_to_iterate_over
            .iter()
            .map(|&value| {
                let maybe_moved_line = if value { vec![] } else { line };
                () // Don't care about the return value
            })
            .collect();

        line.push(2);
    }
}

Первая часть сообщения об ошибке:

error[E0382]: capture of moved value: `line`
 --> src/main.rs:9:67
  |
8 |             .map(|&value| {
  |                  -------- value moved (into closure) here
9 |                 let maybe_moved_line = if value { vec![] } else { line };
  |                                                                   ^^^^ value captured here after move
  |
  = note: move occurs because `line` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

error[E0382]: use of moved value: `line`
  --> src/main.rs:14:9
   |
8  |             .map(|&value| {
   |                  -------- value moved (into closure) here
...
14 |         line.push(2);
   |         ^^^^ value used here after move
   |
   = note: move occurs because `line` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

Как пытается передать сообщение об ошибке, код больше не владеет переменной line после вызова map, потому что право собственности было передано замыканию.

Код пытается передать один Vec кому-то еще, сохраняя его для себя, и это просто не то, как работает право собственности. Я не могу отдать вам свою машину, а потом продолжать ездить на ней каждый день — она не моя!

Наименьшее количество изменений для компиляции вашего кода — это перестать пытаться отдать единственный и неповторимый line, а вместо этого клонировать его по мере необходимости:

  1. Добавьте #[derive(Clone)] к Unit
  2. Клонировать line и world[y] внутри крышки: line.clone(), world[y].clone()

После этого код никогда не отказывается от владения line, поэтому его можно клонировать при необходимости.

person Shepmaster    schedule 08.07.2017
comment
Потрясающий! Я пытался сделать это раньше, но так как не понимал разницы между Copy и Clone. Это означало, что я попытался сделать #[derive(Copy, Clone)] на Unit, но это не сработало. Я решил, что это невозможно, и пошел дальше. Спасибо за помощь, сам бы не разобрался! - person Christopher Dumas; 09.07.2017