ExamplesBy LevelBy TopicLearning Paths
418 Fundamental

418: `stringify!` and `concat!` Macros

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "418: `stringify!` and `concat!` Macros" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Some code patterns require converting source code tokens to string literals at compile time: debug output showing the expression that was evaluated, test assertions printing the failing condition's source text, enum variants converting to their names. Key difference from OCaml: 1. **Source capture**: Rust's `stringify!` captures token text without evaluation; OCaml has no equivalent without PPX.

Tutorial

The Problem

Some code patterns require converting source code tokens to string literals at compile time: debug output showing the expression that was evaluated, test assertions printing the failing condition's source text, enum variants converting to their names. The built-in stringify!($e) macro captures an expression as a &'static str without evaluating it. concat! joins string literals at compile time. Together, these enable zero-runtime-cost reflective string generation from source code.

stringify! is used in assert_eq!'s error messages (printing the two expression texts), dbg! macro, std::env!-based build scripts, and any diagnostic macro that should show "what the programmer wrote."

🎯 Learning Outcomes

  • • Understand how stringify!($expr) captures the token representation of an expression
  • • Learn how concat! joins multiple string literals and stringify! outputs into a single &'static str
  • • See how string_enum! uses stringify! to generate as_str() methods automatically
  • • Understand the difference: stringify!(1 + 2) produces "1 + 2", not "3"
  • • Learn how these macros enable zero-cost reflective debugging
  • Code Example

    let name = stringify!(my_variable);  // "my_variable"
    let expr = stringify!(x + y * z);    // "x + y * z"

    Key Differences

  • Source capture: Rust's stringify! captures token text without evaluation; OCaml has no equivalent without PPX.
  • Compile-time string joining: Rust's concat! joins at compile time; OCaml uses ^ (runtime) or Printf.sprintf (runtime).
  • Location info: Both have file!() / __FILE__ and line!() / __LINE__; Rust's are macros, OCaml's are special values.
  • Zero runtime cost: stringify! and concat! produce &'static str with no runtime allocation; OCaml's equivalent string operations allocate on the GC heap.
  • OCaml Approach

    OCaml's [%string "..."] syntax and Printf.sprintf format strings handle string construction. For reflective output (showing the source expression), OCaml PPX is required — the ppx_expect and ppx_sexp_conv extensions generate test output showing expression text. Without PPX, OCaml has no built-in way to turn an expression into its source text. The __LOC__ and __FUNCTION__ special values provide location information.

    Full Source

    #![allow(clippy::all)]
    //! stringify! and concat! Macros
    //!
    //! Converting tokens to strings at compile time.
    
    /// Debug print with variable name.
    #[macro_export]
    macro_rules! dbg_named {
        ($val:expr) => {{
            let v = $val;
            println!("{} = {:?}", stringify!($val), v);
            v
        }};
    }
    
    /// Assert with expression stringification.
    #[macro_export]
    macro_rules! assert_dbg {
        ($cond:expr) => {
            if !$cond {
                panic!("Assertion failed: {}", stringify!($cond));
            }
        };
    }
    
    /// Create an enum with string conversion.
    #[macro_export]
    macro_rules! string_enum {
        ($name:ident { $($variant:ident),* $(,)? }) => {
            #[derive(Debug, Clone, Copy, PartialEq, Eq)]
            pub enum $name {
                $($variant,)*
            }
    
            impl $name {
                pub fn as_str(&self) -> &'static str {
                    match self {
                        $(Self::$variant => stringify!($variant),)*
                    }
                }
            }
        };
    }
    
    string_enum!(Color { Red, Green, Blue });
    string_enum!(Status {
        Active,
        Inactive,
        Pending
    });
    
    /// Build a path-like string from segments.
    #[macro_export]
    macro_rules! path_str {
        ($($segment:expr),+ $(,)?) => {
            concat!($($segment, "/"),+).trim_end_matches('/')
        };
    }
    
    /// Get function name for logging.
    #[macro_export]
    macro_rules! fn_name {
        () => {{
            fn f() {}
            fn type_name_of<T>(_: T) -> &'static str {
                std::any::type_name::<T>()
            }
            let name = type_name_of(f);
            &name[..name.len() - 3]
        }};
    }
    
    /// Log with automatic location info.
    #[macro_export]
    macro_rules! log_here {
        ($msg:expr) => {
            println!("[{}:{}] {}", file!(), line!(), $msg);
        };
    }
    
    /// Version string from Cargo.toml.
    #[macro_export]
    macro_rules! version_str {
        () => {
            concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"))
        };
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_stringify_basic() {
            let s = stringify!(x + y * z);
            assert_eq!(s, "x + y * z");
        }
    
        #[test]
        fn test_concat_basic() {
            let s = concat!("hello", " ", "world");
            assert_eq!(s, "hello world");
        }
    
        #[test]
        fn test_dbg_named() {
            let x = 42;
            let result = dbg_named!(x + 1);
            assert_eq!(result, 43);
        }
    
        #[test]
        fn test_assert_dbg_pass() {
            assert_dbg!(2 + 2 == 4);
        }
    
        #[test]
        #[should_panic(expected = "2 + 2 == 5")]
        fn test_assert_dbg_fail() {
            assert_dbg!(2 + 2 == 5);
        }
    
        #[test]
        fn test_string_enum() {
            assert_eq!(Color::Red.as_str(), "Red");
            assert_eq!(Color::Blue.as_str(), "Blue");
            assert_eq!(Status::Active.as_str(), "Active");
        }
    
        #[test]
        fn test_file_line() {
            let f = file!();
            let l = line!();
            assert!(f.contains("lib.rs"));
            assert!(l > 0);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_stringify_basic() {
            let s = stringify!(x + y * z);
            assert_eq!(s, "x + y * z");
        }
    
        #[test]
        fn test_concat_basic() {
            let s = concat!("hello", " ", "world");
            assert_eq!(s, "hello world");
        }
    
        #[test]
        fn test_dbg_named() {
            let x = 42;
            let result = dbg_named!(x + 1);
            assert_eq!(result, 43);
        }
    
        #[test]
        fn test_assert_dbg_pass() {
            assert_dbg!(2 + 2 == 4);
        }
    
        #[test]
        #[should_panic(expected = "2 + 2 == 5")]
        fn test_assert_dbg_fail() {
            assert_dbg!(2 + 2 == 5);
        }
    
        #[test]
        fn test_string_enum() {
            assert_eq!(Color::Red.as_str(), "Red");
            assert_eq!(Color::Blue.as_str(), "Blue");
            assert_eq!(Status::Active.as_str(), "Active");
        }
    
        #[test]
        fn test_file_line() {
            let f = file!();
            let l = line!();
            assert!(f.contains("lib.rs"));
            assert!(l > 0);
        }
    }

    Deep Comparison

    OCaml vs Rust: stringify! and concat!

    Rust stringify!

    let name = stringify!(my_variable);  // "my_variable"
    let expr = stringify!(x + y * z);    // "x + y * z"
    

    Rust concat!

    let s = concat!("hello", " ", "world");  // "hello world"
    let path = concat!(env!("HOME"), "/.config");
    

    OCaml Equivalent

    (* No direct equivalent *)
    (* ppx can generate strings from AST *)
    let name = [%string_of_expr my_variable]  (* hypothetical ppx *)
    

    5 Takeaways

  • **stringify! turns tokens into string literals.**
  • **concat! joins strings at compile time.**
  • **file!() and line!() for location info.**
  • Useful for debug macros and assertions.
  • OCaml needs ppx for similar metaprogramming.
  • Exercises

  • Trace macro: Implement trace!(expr) that prints "TRACE: {file}:{line}: {expr_text} = {val:?}" and returns the value. Use it to instrument a sorting algorithm and observe which comparisons are made.
  • Contract macro: Implement requires!(cond, "precondition description") that in debug builds asserts the condition and in release builds is a no-op. Use stringify!(cond) in the assertion message.
  • Enum display: Use string_enum! to define Direction { North, South, East, West } and verify that Direction::North.as_str() == "North". Then implement Display for the enum using the as_str() method.
  • Open Source Repos