arraysrustmoduleidiomshardcode

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


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?"


Solution

  • 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);
    }