在 Rust 模块中使用一些元数据对字节向量进行硬编码的惯用方法是什么?

What is the idiomatic way to hardcode a byte vector with some metadata in a Rust module?

我正在关注 Rust wasm tutorial. in which you build a game-of-life 克隆,目前正在做“用一艘 space 飞船初始化宇宙”练习。

为了实现这艘船,我启动了一个模块,该模块包含船舶数据和相关函数,用于将一艘船绘制到网格中。在这个模块中,我想存储一些预先制作的众所周知的 ships/patterns 例如 copperhead ship.

对于数据结构,我提出了以下结构:

// life_patterns.rs
pub struct LifePattern {
  width: u32,
  height: u32,
  data: Vec<u8>,
}

现在我想将实际数据硬编码到模块中。来自 JavaScript 的背景,我想到了:

// life_patterns.rs
pub const COPPERHEAD: LifePattern = LifePattern {
  width: 10,
  height: 13,
  data: vec![
    0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
    1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
    0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    ],
}

然后我想将图案绘制到现有网格中,如下所示:

// lib.rs
life_patterns::draw(grid, start_index, life_patterns::COPPERHEAD);

我的解决方案无法编译并显示错误消息:

allocations are not allowed in constants E0010
calls in constants are limited to constant functions, tuple structs and tuple variants E0015

那么现在我的问题是,如何以惯用的方式在 life_patterns 模块中正确硬编码铜斑蛇船的数据?

一个更通用的提问方式可能是:“我如何在 Rust 模块中惯用地硬编码一个 Vec 和两个 u32?”

我会说,格式尽可能human-readable,让计算机在运行时转换它。

// pattern is clearly visible
pub const COPPERHEAD: &'static[&'static str] = &[
    "____11____",
    "___1111___",
    "__________",
    "__111111__",
    "___1111___",
    "__________",
    "__11__11__",
    "11_1__1_11",
    "___1__1___",
    "__________",
    "__________",
    "____11____",
    "____11____",
];

pub struct LifePattern {
    width: u32,
    height: u32,
    data: Vec<u8>,
}

impl From<&[&str]> for LifePattern {
    fn from(pattern: &[&str]) -> Self {
        let width = pattern.first().map(|s| s.len()).unwrap_or(0) as u32;
        let height = pattern.len() as u32;
        
        let mut data = Vec::with_capacity(width as usize * height as usize);
        for line in pattern {
            for c in line.chars() {
                data.push((c != '_') as u8);
            }
        }

        Self {
            width,
            height,
            data,
        }
    }
}

那你就做

life_patterns::draw(grid, start_index, life_patterns::COPPERHEAD.into());

为了接近您在问题中显示的用法,我会使用 lazy_static

它的目的是在初始化与const不兼容时提供类似于const的东西;然后它在 run-time.

发生一次

编辑

@Caesar 的一个非常有趣的评论建议依赖 once_cell 这应该成为标准。

另一个答案提出了一个可读的模式,我认为这是一个非常好的主意。

该示例将原始解决方案保留为注释,并根据之前的两条评论提出另一个解决方案。

pub struct LifePattern {
    width: u32,
    height: u32,
    data: Vec<u8>,
}

/*
lazy_static::lazy_static! {
    pub static ref COPPERHEAD: LifePattern = LifePattern {
        width: 10,
        height: 13,
        data: vec![
            0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
            0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
            0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
            1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
            0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
            ],
    };
}
*/

static COPPERHEAD: once_cell::sync::Lazy<LifePattern> =
    once_cell::sync::Lazy::new(|| LifePattern {
        width: 10,
        height: 13,
        data: "\
            ____XX____\
            ___XXXX___\
            __________\
            __XXXXXX__\
            ___XXXX___\
            __________\
            __XX__XX__\
            XX_X__X_XX\
            ___X__X___\
            __________\
            __________\
            ____XX____\
            ____XX____"
            .chars()
            .map(|c| if c == 'X' { 1 } else { 0 })
            .collect(),
    });

fn main() {
    let pattern: &LifePattern = &COPPERHEAD;
    println!("with={}", pattern.width);
    println!("height={}", pattern.height);
    println!("data={:?}", pattern.data);
}