Process external files in const fn: no build.rs, no proc macros, no binary bloat

134 points carlk22 13 comments

Here’s a fun Rust trick I’ve been experimenting with for embedded work:

You can use include_bytes!() inside a const fn, to process file contents at compile time, and keep only the final result in your binary.

No build.rs. No proc macros. No runtime cost.

Minimal example

const fn sum_u16s() -> u128 {
    let data: &[u8; 8] = include_bytes!("data.bin");

    assert!(data.len() % 2 == 0);

    let mut i = 0;
    let mut acc: u128 = 0;

    while i < data.len() {
        // interpret two bytes as little-endian u16
        let value = (data[i] as u16)
            | ((data[i + 1] as u16) << 8);

        acc += value as u128;
        i += 2;
    }
    acc
}

static SUM: u128 = sum_u16s();

What’s happening:

  • include_bytes!() reads the file at compile time.
  • The loop runs entirely in const evaluation.
  • The compiler computes SUM during compilation.
  • Only the u128 result is stored in the final binary.

If you remove the static SUM, the file contributes zero bytes to the binary (release build). It’s just compile-time input.

Why this is interesting

For embedded Rust, this effectively gives you a tiny compile-time asset pipeline:

  • Read raw data files (audio, lookup tables, calibration data, etc.)
  • Validate them
  • Transform them (even some audio compression)
  • Materialize only the final representation you actually need

And you only pay flash space for what you explicitly store.
It’s surprisingly powerful and it’s all stable Rust today.