R
RUSTY YELLOW PAGES

include! / include_str! / include_bytes!Macro


Explanation

All three splice the contents of another file into the current one at compile time, differing only in what shape that content takes. include!("path.rs") parses the named file's contents as literal Rust source and substitutes it in place, as if it had been typed directly at that position — used almost exclusively for source generated by a build script (typically read back in from env!("OUT_DIR")), since anything hand-written is normally reached for with an ordinary mod instead. include_str!("file.txt") embeds the named file's entire contents as a &'static str, verified to be valid UTF-8 at compile time (a non-UTF-8 file is a compile error). include_bytes!("file.bin") embeds the file's raw bytes as a &'static [u8; N], with no encoding requirement at all — the array's length N is the file's exact byte count, known at compile time.

The path in all three is resolved relative to the current file, not the crate root — a common gotcha, since most other path-like things in Rust (module paths, use statements) are crate-rooted. include_str!("templates/welcome.txt") inside src/email/mod.rs looks for src/email/templates/welcome.txt, not a templates/ folder at the crate root.

Usage examples

Embedding a text template and a binary file at compile time

const WELCOME_TEMPLATE: &str = include_str!("welcome_template.txt"); // <- resolved relative to *this* file, not the crate root
const LOGO_PNG: &[u8] = include_bytes!("logo.png");                  // <- embedded as a byte array, no encoding requirement

Designing a public API

A CLI tool embeds its default config as a template at compile time, so the binary is self-contained and needs no external file to run --init.

const DEFAULT_CONFIG_TEMPLATE: &str = include_str!("default_config.toml");
// <- baked into the binary at compile time; no runtime file lookup, no missing-file failure mode

fn write_default_config(path: &std::path::Path) -> std::io::Result<()> {
    std::fs::write(path, DEFAULT_CONFIG_TEMPLATE)
}

The std docs note that embedding a template at compile time removes an entire runtime failure mode — the template file being missing or misplaced relative to the installed binary — at the cost of needing a rebuild to change it.

Testing

A parser's test suite loads its sample input from a fixture file instead of inlining a large literal string in the test source.

fn parse_line_count(input: &str) -> usize {
    input.lines().count()
}

#[test]
fn counts_lines_in_fixture() {
    let sample = include_str!("fixtures/sample_log.txt"); // <- path is relative to this test file, not the crate root
    assert_eq!(parse_line_count(sample), 42);
}

Keeping fixture data in its own file, loaded via include_str!, rather than a large inline string literal keeps the test function readable and lets the fixture be viewed, edited, and diffed as ordinary text — a shape the Rust Cookbook's recipe-style examples favor for anything beyond a couple of lines.

Explanation

Embedded support: Full

All three remain pure compile-time file embedding with zero runtime cost under #![no_std], and include_bytes! in particular is one of the more distinctly embedded-relevant macros here: baking a firmware blob, a font, a lookup table, or a calibration table directly into the binary as a &'static [u8; N] costs nothing at runtime — no filesystem, no loader, no heap — and the data ends up living in flash/ROM alongside the code, exactly where a constant embedded table belongs.

include! shows up in embedded builds most often paired with env!("OUT_DIR") (see env! / option_env!): peripheral-access crates generated by svd2rust from a chip vendor's SVD file typically have a build.rs write the generated register-definition source into OUT_DIR, and the crate splices it in with include!(concat!(env!("OUT_DIR"), "/device.rs")) rather than checking thousands of lines of generated code into the repository.

Usage examples

Baking a lookup table into flash with no heap

const SINE_TABLE: &[u8; 256] = include_bytes!("sine_table.bin"); // <- lives in flash/ROM, zero runtime cost, no allocator needed

fn dac_output(phase: u8) -> u8 {
    SINE_TABLE[phase as usize]
}

Splicing in build-script-generated peripheral definitions

// build.rs (run on the host, before the firmware itself compiles) writes the
// SVD-derived register definitions for this exact chip into OUT_DIR.
include!(concat!(env!("OUT_DIR"), "/device.rs")); // <- splices in generated struct/impl items at compile time