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

425 views Asked by At

I am following the Rust wasm tutorial. in which you build a game-of-life clone and am currently doing the "Initialize the universe with a single space ship" exercise.

To implement the ship I started a module which holds the ship data and associated functions to draw a ship to a grid. In this module I want to store some pre-made well known ships/patterns as for example the copperhead ship.

For the data structure I came up with following struct:

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

Now I want to hardcode the actual data into the module. Coming from a JavaScript background I came up with:

// 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,
    ],
}

I want then draw the pattern to an existing grid like so:

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

My solution doesn't compile with the error messages:

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

So now my question, how do I properly hardcode the data for the copperhead ship in the life_patterns module in an idiomatic way?

A more general way to ask this could be: "How do I hardcode an Vec<u8> and two u32 in a Rust module idiomaticly?"

2

There are 2 answers

1
prog-fh On BEST ANSWER

In order to stay close to the usage you show in your question, I would use lazy_static.

Its purpose is to provide something similar to const when the initialisation is not compatible with const; it then happens once for all at run-time.


edit

A very interesting remark of @Caesar suggest relying on once_cell which should become standard.

The other answer suggests a readable pattern, which is a very good idea in my opinion.

The example keeps the original solution as a comment and suggests another solution considering the two previous remarks.

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);
}
1
Jakub Dóka On

I would say, make the format as human-readable as possible and let the computer convert it at runtime.

// 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,
        }
    }
}

then you do

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