๐Ÿฆ€ Functional Rust

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:

What This Unlocks

Key Differences

ConceptOCamlRust
Embed file at compile timeNot built-in; use `ppx` or manual `read_file` at startup`include_str!` / `include_bytes!` โ€” standard macros, no deps
Missing fileRuntime error (if read at startup)Compile error
Result type`string` (heap) loaded at runtime`&'static str` / `&'static [u8]` โ€” baked into binary
Binary filesRead 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)