Instantiating a 2d Vec in a Struct?

2019-09-01 16:53发布

问题:

I'm having trouble instantiating a vec when using a constructor to return a new struct object. The syntax I've tried (using collect() improperly, probably) spat out a ton of compiler errors.

fn main() {
    let level = Level::new();
}

struct Level {
    tiles: Vec<Vec<Tile>>
}

struct Tile {
    idx: i32
}

impl Level {
    fn new() -> Level {
        Level {
            tiles: {
            let mut t = Vec::new();
            let mut t2 = Vec::new();
            for x in range(0, 80) {
                for y in range(0, 24) {
                    t2.push(Tile::new(x, y));
                }
                t.push(t2);
            }
            t
        }
    }
}

impl Tile {
    fn new(x: i32, y: i32) -> Tile {
        Tile { pos: Point { x: x, y: y } }
    }
}

struct Point {
    x: i32,
    y: i32
}

I get these errors:

src/game/dungeon/level/mod.rs:47:25: 47:27 error: use of moved value: `t2`
src/game/dungeon/level/mod.rs:47                 t2.push(Tile::new(x, y));
                                                     ^~
src/game/dungeon/level/mod.rs:49:28: 49:30 note: `t2` moved here because it has type `collections::vec::Vec<game::dungeon::level::Tile>`, which is non-copyable
src/game/dungeon/level/mod.rs:49                     t.push(t2);
                                                        ^~
src/game/dungeon/level/mod.rs:49:28: 49:30 error: use of moved value: `t2`
src/game/dungeon/level/mod.rs:49                     t.push(t2);
                                                        ^~
src/game/dungeon/level/mod.rs:49:28: 49:30 note: `t2` moved here because it has type `collections::vec::Vec<game::dungeon::level::Tile>`, which is non-copyable
src/game/dungeon/level/mod.rs:49                     t.push(t2);
                                                        ^~

回答1:

Yes, you're doing it incorrectly. The similar code will also be incorrect in C/C++, BTW.

        let mut t = Vec::new();
        let mut t2 = Vec::new();
        for x in range(0, 80) {
            for y in range(0, 24) {
                t2.push(Tile::new());
            }
            t.push(t2);
        }

The problem is, you're always pushing into the same t2 in the inner loop and then you're always pushing the same t2 into t. The latter is a violation of ownership semantics, so Rust compiler correctly tells you about using a moved value.

The idiomatic approach is to use iterators and it could look like this:

(0..80).map(|_| (0..24).map(|_| Tile::new()).collect()).collect()

If you need to access indices you can use map() closure arguments:

(0..80).map(|x| (0..24).map(|y| Tile::new(x, y)).collect()).collect()

The compiler should automatically deduce the desired type of collect() result.



回答2:

Vladimir's answer is really nice, however I have a feeling that the functional style might hide the error here.

You are actually not far from the solution; the issue is simply that you cannot reuse the same t2 at each iteration of the outer loop. The simplest transformation, therefore, is to create t2 inside the outer loop:

impl Level {
    fn new() -> Level {
        Level {
            tiles: {
            let mut t = Vec::new();
            for x in range(0, 80) {
                let mut t2 = Vec::new(); // Moved!
                for y in range(0, 24) {
                    t2.push(Tile::new(x, y));
                }
                t.push(t2);
            }
            t
        }
    }
}


标签: rust