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.