#251 Jul 10, 2026

251. include_str! — Ship the File Inside the Binary, Skip the Runtime Read

A missing template or SQL file in the deploy takes your app down at startup. include_str! bakes the file into the binary at compile time — a missing file becomes a compile error, not a production incident.

The runtime way — and its failure mode

1
2
3
// Runs at startup: I/O, error handling, and the
// file must be shipped alongside the executable.
let template = std::fs::read_to_string("greeting.txt")?;

This works until someone forgets to copy greeting.txt into the container, or the working directory isn’t what you assumed. The failure shows up at runtime, on someone else’s machine.

The compile-time way

1
2
// Embedded in the executable at compile time.
static TEMPLATE: &str = include_str!("greeting.txt");

The file’s contents become a &'static str inside your binary. No I/O, no Result, nothing to deploy alongside. If the file is missing or isn’t valid UTF-8, cargo build fails — the mistake never leaves your machine.

For binary assets there is include_bytes!, which gives you a &'static [u8]:

1
static LOGO: &[u8] = include_bytes!("logo.png");

Path gotcha

The path is relative to the current source file, not the crate root or working directory. For paths that survive refactoring into submodules, anchor them to the manifest:

1
2
3
static QUERY: &str = include_str!(
    concat!(env!("CARGO_MANIFEST_DIR"), "/queries/get_user.sql")
);

When to reach for it

SQL queries, HTML templates, license text, shader source, test fixtures — anything small and fixed at build time. Skip it for files that must be user-editable after deployment, or big enough to bloat the binary noticeably: embedding means every change requires a recompile. That’s the trade — and for config that should never drift from the code, it’s exactly what you want.

← Previous 250. lowest_one / highest_one — Set-Bit Positions as an Option, Not a Sentinel Next → 252. Wrapping<T> — Modular Arithmetic Without the .wrapping_add() Noise