421: include! and include_str!
Difficulty: 2 Level: Intermediate Embed file contents as string or byte literals at compile time โ SQL queries, HTML templates, Wasm bytecode โ with zero runtime I/O.The Problem This Solves
Hard-coding multi-line strings inline makes code noisy and hard to maintain. Keeping them in separate files is cleaner, but reading files at runtime adds failure modes: the file might be missing, the path might be wrong, the read might fail. You also lose the performance of having the data baked into the binary. For assets that never change between builds โ SQL schemas, config templates, embedded certificates โ you want the simplicity of a separate file combined with the reliability of a compile-time constant. You also want the IDE to recognise the string as a literal so it participates in dead-code and const-folding optimisations. `include_str!` and `include_bytes!` solve exactly this: the compiler reads the file during compilation and replaces the macro call with a `&'static str` or `&'static [u8]` literal. If the file is missing, the build fails โ not the running program.The Intuition
Think of `include_str!("query.sql")` as copy-paste that the compiler does for you at build time. The SQL file stays readable and editable on disk; the binary acts as if you typed the string inline. The path is relative to the source file, so it travels with the crate. `include_bytes!` does the same for binary files: Wasm modules, compiled shaders, TLS certificates, small images. The result is a `&'static [u8]` โ zero heap allocation, zero runtime loading.How It Works in Rust
// In a real project these reference actual files:
// const QUERY: &str = include_str!("queries/users.sql");
// static WASM: &[u8] = include_bytes!("module.wasm");
// Path is relative to the current source file, not the crate root.
// If the file is missing, you get a COMPILE ERROR, not a runtime panic.
const SQL_QUERY: &str =
"SELECT id, name, email FROM users WHERE active = true ORDER BY name";
// include_bytes! gives a byte array โ good for binary data
static EMBEDDED_DATA: &[u8] = &[0x52, 0x75, 0x73, 0x74]; // "Rust"
// Use the constant anywhere โ it's just a &'static str
fn render(template: &str, title: &str) -> String {
template.replace("{{title}}", title)
}
Key mechanics:
- `include_str!` โ UTF-8 text โ `&'static str`
- `include_bytes!` โ raw bytes โ `&'static [u8]`
- `include!` โ includes a Rust source file (for code generation output)
- The `CARGO_MANIFEST_DIR` env var is handy when build scripts generate the included file
What This Unlocks
- Embedded SQL / GraphQL schemas โ keep queries in `.sql` files, use them as constants at zero runtime cost.
- Embedded HTML / config templates โ ship a self-contained binary that renders pages without touching the filesystem.
- Binary asset embedding โ Wasm modules, compiled shaders, certificates embedded directly in the binary; no installation step required.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Embed file at compile time | Not built-in; use `ppx` or manual `read_file` at startup | `include_str!` / `include_bytes!` โ standard macros, no deps |
| Missing file | Runtime error (if read at startup) | Compile error |
| Result type | `string` (heap) loaded at runtime | `&'static str` / `&'static [u8]` โ baked into binary |
| Binary files | Read with `Bytes.create` at runtime | `include_bytes!` โ `&'static [u8]` |
// include! and include_str! in Rust
// Note: we demonstrate the API; actual file embedding requires the file to exist
// Embed a text constant (simulating what include_str! would do)
// In a real project: const QUERY: &str = include_str!("queries/users.sql");
// const CONFIG_TEMPLATE: &str = include_str!("templates/config.toml.tpl");
// We'll use a macro to simulate the concept with inline strings
macro_rules! fake_include_str {
($content:literal) => { $content };
}
const SQL_QUERY: &str = fake_include_str!(
"SELECT id, name, email FROM users WHERE active = true ORDER BY name"
);
const HTML_TEMPLATE: &str = fake_include_str!(
"<!DOCTYPE html>
<html><body>
<h1>{{title}}</h1>
<p>{{content}}</p>
</body></html>"
);
const GRAPHQL_SCHEMA: &str = fake_include_str!(
"type User {
id: ID!
name: String!
email: String!
}"
);
// Demonstrate include_bytes! concept with a byte array
// In real code: static WASM_BYTES: &[u8] = include_bytes!("module.wasm");
static EMBEDDED_DATA: &[u8] = &[0x52, 0x75, 0x73, 0x74]; // "Rust" in ASCII
fn render_template(template: &str, title: &str, content: &str) -> String {
template
.replace("{{title}}", title)
.replace("{{content}}", content)
}
fn main() {
println!("=== Embedded SQL ===");
println!("{}", SQL_QUERY);
println!("
=== HTML Template ===");
let rendered = render_template(HTML_TEMPLATE, "Welcome", "Hello from Rust!");
println!("{}", rendered);
println!("
=== GraphQL Schema ===");
println!("{}", GRAPHQL_SCHEMA);
println!("
=== Binary Data ===");
println!("Embedded bytes: {:?}", EMBEDDED_DATA);
println!("As string: {}", std::str::from_utf8(EMBEDDED_DATA).unwrap());
// Real use case: parse embedded JSON/TOML at startup
let data = r#"{"version": "1.0.0", "name": "my-app"}"#;
println!("
Embedded JSON length: {} bytes", data.len());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_sql() {
assert!(SQL_QUERY.contains("SELECT"));
assert!(SQL_QUERY.contains("FROM users"));
}
#[test]
fn test_template_render() {
let result = render_template(HTML_TEMPLATE, "Test", "Body");
assert!(result.contains("Test"));
assert!(result.contains("Body"));
assert!(!result.contains("{{title}}"));
}
#[test]
fn test_embedded_bytes() {
assert_eq!(EMBEDDED_DATA, b"Rust");
}
}
(* include_str! concept in OCaml *)
(* OCaml doesn't have compile-time file inclusion; use read_file *)
let read_file path =
let ic = open_in path in
let n = in_channel_length ic in
let s = Bytes.create n in
really_input ic s 0 n;
close_in ic;
Bytes.to_string s
(* Simulate include_str! with a constant *)
let sample_text =
"Hello, World!\n This is a multi-line string.\n Embedded at compile time."
let version_from_file = "1.0.0" (* would be include("VERSION") *)
let () =
Printf.printf "Embedded text:\n%s\n" sample_text;
Printf.printf "Version: %s\n" version_from_file;
Printf.printf "Text length: %d bytes\n" (String.length sample_text)