ExamplesBy LevelBy TopicLearning Paths
421 Fundamental

421: `include!`, `include_str!`, `include_bytes!`

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "421: `include!`, `include_str!`, `include_bytes!`" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Binary assets (images, shaders, SQL queries, configuration files) can be embedded directly into executables. Key difference from OCaml: 1. **Built

Tutorial

The Problem

Binary assets (images, shaders, SQL queries, configuration files) can be embedded directly into executables. Instead of loading them from disk at runtime (which can fail if the file is missing or the path changes), include_bytes! and include_str! embed the file content as a compile-time constant. The resulting binary is self-contained: no external files needed, no file-not-found errors at runtime. include! includes arbitrary Rust source files, enabling code generation workflows where a build.rs produces Rust code that is then include!'d.

These macros are used by rust-embed, WASM binary bundlers, shader compilers, and any application embedding resources (game assets, localization strings, TLS certificates).

🎯 Learning Outcomes

  • • Understand how include_bytes! embeds a file as &'static [u8] at compile time
  • • Learn how include_str! embeds a UTF-8 file as &'static str
  • • See how include! includes generated Rust source code from build.rs workflows
  • • Understand how Cargo tracks file dependencies via println!("cargo:rerun-if-changed=...")
  • • Learn the trade-off: larger binary vs. zero runtime file I/O errors
  • Code Example

    #![allow(clippy::all)]
    //! include! and include_str! Macros
    //!
    //! Including files at compile time.
    
    /// Include raw bytes from a file.
    pub const README_BYTES: &[u8] = include_bytes!("../Cargo.toml");
    
    /// Example SQL query.
    pub fn example_query() -> &'static str {
        "SELECT * FROM users"
    }
    
    /// Include at compile time.
    #[macro_export]
    macro_rules! include_sql {
        ($name:literal) => {
            concat!("-- Query: ", $name, "\n", "SELECT * FROM ", $name)
        };
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_include_bytes() {
            assert!(!README_BYTES.is_empty());
        }
    
        #[test]
        fn test_include_sql() {
            let sql = include_sql!("users");
            assert!(sql.contains("users"));
        }
    
        #[test]
        fn test_example_query() {
            assert!(example_query().contains("SELECT"));
        }
    
        #[test]
        fn test_cargo_toml_content() {
            let s = std::str::from_utf8(README_BYTES).unwrap();
            assert!(s.contains("[package]"));
        }
    
        #[test]
        fn test_file_macro() {
            let f = file!();
            assert!(f.contains("lib.rs"));
        }
    }

    Key Differences

  • Built-in: Rust's include_bytes! and include_str! are language primitives; OCaml requires build system configuration or external libraries.
  • Path resolution: Rust resolves paths relative to the source file; OCaml's approaches resolve relative to the dune build directory.
  • Rebuild tracking: Cargo automatically re-builds when included files change; OCaml requires explicit (deps ...) in dune rules.
  • Code inclusion: Rust's include! includes arbitrary Rust code; OCaml's load_path and dynamic loading serve similar purposes but differently.
  • OCaml Approach

    OCaml achieves file embedding through the dune build system's (embed_file ...) rule or through the sedlex and menhir grammar inclusion patterns. The Crunch library converts files into OCaml modules. For inline SQL, ppx_rapper generates type-safe queries from SQL strings in OCaml source. OCaml has no built-in equivalent of include_bytes! — file embedding requires build system configuration.

    Full Source

    #![allow(clippy::all)]
    //! include! and include_str! Macros
    //!
    //! Including files at compile time.
    
    /// Include raw bytes from a file.
    pub const README_BYTES: &[u8] = include_bytes!("../Cargo.toml");
    
    /// Example SQL query.
    pub fn example_query() -> &'static str {
        "SELECT * FROM users"
    }
    
    /// Include at compile time.
    #[macro_export]
    macro_rules! include_sql {
        ($name:literal) => {
            concat!("-- Query: ", $name, "\n", "SELECT * FROM ", $name)
        };
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_include_bytes() {
            assert!(!README_BYTES.is_empty());
        }
    
        #[test]
        fn test_include_sql() {
            let sql = include_sql!("users");
            assert!(sql.contains("users"));
        }
    
        #[test]
        fn test_example_query() {
            assert!(example_query().contains("SELECT"));
        }
    
        #[test]
        fn test_cargo_toml_content() {
            let s = std::str::from_utf8(README_BYTES).unwrap();
            assert!(s.contains("[package]"));
        }
    
        #[test]
        fn test_file_macro() {
            let f = file!();
            assert!(f.contains("lib.rs"));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_include_bytes() {
            assert!(!README_BYTES.is_empty());
        }
    
        #[test]
        fn test_include_sql() {
            let sql = include_sql!("users");
            assert!(sql.contains("users"));
        }
    
        #[test]
        fn test_example_query() {
            assert!(example_query().contains("SELECT"));
        }
    
        #[test]
        fn test_cargo_toml_content() {
            let s = std::str::from_utf8(README_BYTES).unwrap();
            assert!(s.contains("[package]"));
        }
    
        #[test]
        fn test_file_macro() {
            let f = file!();
            assert!(f.contains("lib.rs"));
        }
    }

    Deep Comparison

    OCaml vs Rust: macro include

    See example.rs and example.ml for side-by-side implementations.

    Key Points

  • Rust macros operate at compile time
  • OCaml uses ppx for similar metaprogramming
  • Both languages support powerful code generation
  • Rust's macro_rules! is built into the language
  • OCaml's approach requires external tooling
  • Exercises

  • Embedded config: Use include_str! to embed a TOML config file into your binary. Parse it at startup using the toml crate and expose configuration values as constants. Show that the binary works without the config file being present at runtime.
  • Shader embedding: Embed a GLSL vertex and fragment shader as include_str! constants. Create a ShaderSource { vert: &'static str, frag: &'static str } and verify the strings contain valid GLSL keywords.
  • Code generation: Write a build.rs that reads a data/keywords.txt file and generates src/generated.rs containing pub const KEYWORDS: &[&str] = &[...];. Use include!("../src/generated.rs") to include the generated code.
  • Open Source Repos