Macros

#251 Jul 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.

171. assert_matches! — A Test Failure That Actually Tells You What Went Wrong

assert!(matches!(x, Foo::Bar)) panics with assertion failed: matches!(x, Foo::Bar) and zero hint about what x actually was. Rust 1.96 stabilises assert_matches!, which prints the offending value for you.

The old way leaves you guessing

The classic assert!(matches!(...)) combo has been in tests forever, but its failure message is useless:

1
2
3
4
5
6
#[derive(Debug)]
enum Status { Ok, Pending, Failed(u32) }

let s = Status::Failed(42);
assert!(matches!(s, Status::Ok));
// panic: assertion failed: matches!(s, Status :: Ok)

When this fires in CI, you get the pattern back but not the value. Was it Pending? Failed? With what code? You either rerun with dbg! or eyeball the test setup.

assert_matches! includes the value

assert_matches! lives in core (and std) as of 1.96. It checks the same way, but on failure it prints the Debug representation of what you handed it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::assert_matches::assert_matches;

#[derive(Debug)]
enum Status { Ok, Pending, Failed(u32) }

let s = Status::Failed(42);
assert_matches!(s, Status::Ok);
// panic: assertion `left matches right` failed
//   left: Failed(42)
//  right: Status::Ok

Same one-liner, real diagnostic. No extra dbg!, no rerun.

Pattern guards still work

Because it’s a real pattern position, you get bindings and guards too — useful when you want “some variant with a value in a range”:

1
2
3
4
use std::assert_matches::assert_matches;

let n: Result<i32, &str> = Ok(7);
assert_matches!(n, Ok(x) if x > 0);

The guard is checked just like in a match arm, and the panic message still shows you the value if it fails.

Heads up: not in the prelude

Unlike assert! and assert_eq!, assert_matches! is not in the prelude — too many third-party crates (notably the assert_matches crate) already export the same name. Import it explicitly:

1
2
3
use std::assert_matches::assert_matches;
// or, in no_std:
// use core::assert_matches::assert_matches;

There’s also debug_assert_matches! for the same trick that compiles away in release builds.

Stabilised in Rust 1.96 (May 2026). Delete one third-party dependency from your dev-dependencies today.