0

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

Darvan42
  • 53
  • 1
  • 4

2 Answers2

1

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());
Jakub Dóka
  • 2,477
  • 1
  • 7
  • 12
  • Thank you very much for this answer! I like the approach of human readability very much. However I will mark the other answer as the accepted answer as it shows the generic use case with a vector, which is closer to the title of my question, and might help others who are searching a solution for vectors in another use case. Nevertheless thank you! – Darvan42 Apr 09 '22 at 13:04
1

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);
}
prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • 2
    Do note that the `lazy_static` alternative `once_cell` is about to make its way into [std](https://doc.rust-lang.org/stable/std/lazy/struct.Lazy.html#method.new), so it's probably better to use that… – Caesar Apr 09 '22 at 13:57