如何在 Rust 的结构体中实现动态二维数组?

pha*_*hor 5 arrays malloc rust

我对 Rust 很陌生,我想实现一个 2D 地图。因此,我有一个具有内部宽度、高度和未知大小的数据指针的映射结构,直到调用解析文件的 load_map 函数,然后初始化映射结构。在C语言中我会这样做:

struct map {
  uint32_t width;
  uint32_t height;
  uint8_t *data;
};

int load_map(struct map *m)
{
   ... parse a file and found a map with height=width=8
   m->width = 8;
   m->height = 8;
   // allocate map data using the map size we found
   m->data = (uint8_t *)malloc(m->width * m->height);
   ...
}
Run Code Online (Sandbox Code Playgroud)

在 Rust 中,我不知道如何实现这么简单的事情!这是我失败的尝试:

struct Map<'a> {
    width: usize,
    height: usize,
    data: &'a mut u8
}

fn load_map() -> Map<'static>
{
    //code that parse a file and found a map with height=width=8
    ...

    const w:usize = 8;
    const h:usize = 8;

    //allocate the map data
    let mut data = [0;w*h];

    // create the map structure
    let mut m = Map {
        width: w,
        height: h,
        data: data
    };

    m
}

fn main() {
    let mut m: Map;
    m = load_map();
    println!("Hello, world! {} {}", m.height, m.width);
}
Run Code Online (Sandbox Code Playgroud)

但货物不高兴并向我抛出这个错误:

error[E0308]: mismatched types
  --> src/main.rs:16:15
   |
16 |         data: data
   |               ^^^^ expected `&mut u8`, found array `[{integer}; 64]`

error: aborting due to previous error
Run Code Online (Sandbox Code Playgroud)

我确实理解这个错误:rust 期望数据具有相同类型的数据。但我不知道如何实现这一目标。Map 结构是通用的,在解析文件之前我们无法知道数据大小。看来我们应该能够在 init_map 中声明数据字节并将所有权赋予 Map 结构,对吗?但如何呢?

Kev*_*eid 3

免责声明:我自己对 Rust 很陌生;我只是碰巧正在研究这个完全相同的问题,因此学到了一些关于它的知识。除了我在这里建议的之外,可能还有更好的解决方案或替代方案。

如果在 C 中你有一个指向数组的指针

  uint8_t *data;
Run Code Online (Sandbox Code Playgroud)

它由malloc包含结构初始化并期望具有相同的生命周期,那么你在 Rust 中编写它的方式不是data: &u8

    data: Box<[u8]>,
Run Code Online (Sandbox Code Playgroud)

这与您的尝试有两个不同之处:

  1. 它有[u8]而不是u8,因此它存储了许多元素而不是仅仅一个。
  2. Box<...>代替了&...,因此它拥有它所指的内容。

创建 Box 会在堆上分配其内容,因此直接对应于 的使用malloc。在这里,我修改了您的代码以显示此工作原理并处理所有警告:

struct Map {
    width: usize,
    height: usize,
    data: Box<[u8]>,
}

fn load_map() -> Map {
    const W: usize = 8;
    const H: usize = 8;

    //allocate the map data
    let data = [0;W*H];

    // create the map structure
    let m = Map {
        width: W,
        height: H,
        data: Box::new(data)
    };

    m
}

fn main() {
    let m: Map = load_map();
    println!("Hello, world! {} {} {}", m.height, m.width, m.data[0]);
}
Run Code Online (Sandbox Code Playgroud)

还要注意,显式的生命周期参数<'a>'static消失了;现在不需要它们,并且它们不会起作用(因为函数分配的东西不能起作用'static,除非您选择泄漏它)。

但是,这仍然需要const宽度和高度来构造数组,一旦从地图文件中读取宽度和高度,数组就无法工作。为了解决这个问题,您需要以不同的方式构造数组(或更准确地说,数组切片);一种选择是使用Vec构造函数(然后将其简化为数组切片,因为您不打算更改长度):

    data: vec![0; w * h].into_boxed_slice(),
Run Code Online (Sandbox Code Playgroud)

  • @Jesper我选择了“Box &lt;[u8]&gt;”,因为它不允许更改长度,这通常是对矩形地图数据的错误操作;人们希望同时更新“width”、“height”和“data”,这将是对整个“Map”而不是包含的“Vec”的操作。 (3认同)