arraysrustmacroscompile-time-constant

Load file contents into a static array of bytes


I have a static array initialized with some constant value:

static PROG_ROM: [u8; 850] = [0x12, 0x1d, ...];

I would like to instead load at compile-time the contents of a file into it. Sounds like a job for std::include_bytes!, however, I have two problems with it:

  1. The type of include_bytes!("foo.dat") is &[u8; 850] i.e. it is a reference. I need this to be a bonafide static array.

  2. Even if there was an include_bytes_static! macro with type [u8;850], I would have to use it like this:

    static PROG_ROM: [u8; 850] = include_bytes_static!("foo.dat");
    

    I.e. I would have to hardcode the length of the file. Instead, I would like to take the length from the length of the file contents.

So the ideal replacement for my code would be a macro to replace the whole definition, i.e. look something like this:

define_included_bytes!(PROG_ROM, "foo.dat")

and it would expand to

static PROG_ROM: [u8; 850] = [0x12, 0x1d, ...];

So how do I do this?


Solution

  • As Chayim Friedman pointed out you can easily define that proc macro yourself:

    #[proc_macro]
    pub fn define_included_bytes(token_stream: TokenStream) -> TokenStream {
        let [ident, _comma, file] = &token_stream.into_iter().collect::<Vec<_>>()[..] else {
            panic!("expected invocation: `define_included_bytes!(IDENTIFIER, \"file_name\");");
        };
        let file = file.to_string().trim_matches('\"').to_string();
        let data: Vec<u8> = std::fs::read(&file).expect(&format!("File {:?} could not be read", file));
        format!("const {ident}: [u8; {}] = {:?};", data.len(), data).parse().unwrap()
    }
    

    Obviously this is just a hacked together proof of concept and you should thouroughly check the tokens instead of just assuming they're correct.