๐Ÿฆ€ Functional Rust

418: stringify! and concat!

Difficulty: 2 Level: Intermediate Turn source code into strings and join string literals at compile time โ€” the bridge between code and text.

The Problem This Solves

Sometimes you need the source text of an expression, not its value. `assert_eq!(a, b)` shows `"left == right: a = 42, b = 43"` โ€” it knows what `a` and `b` are named in the source. A regular function can't do this: it only receives values, not the identifiers that produced them. `stringify!` captures an expression, identifier, type, or any token sequence and produces it as a string literal โ€” the source text, verbatim. `concat!` joins string literals (including those from `stringify!`, `file!`, `line!`) into a single compile-time constant. Together they let you build rich diagnostic messages, generate human-readable names from identifiers, and produce SQL-like strings from Rust code โ€” all without any runtime cost.

The Intuition

`stringify!` doesn't evaluate its input. It captures the token sequence and turns it into a `&str` literal. `stringify!(1 + 1)` is `"1 + 1"`, not `"2"`. `stringify!(my_var)` is `"my_var"`, not the value of the variable. This is the key distinction from any runtime operation โ€” you're operating on syntax, not values. `concat!` is similar: it joins literal strings at compile time into a single `&str`. Since all arguments must be compile-time constants (`literal` or other compile-time macros), there's no runtime allocation. The resulting string is baked into the binary. Together with `file!`, `line!`, `column!`, and `env!`, they provide a toolkit for compile-time string construction โ€” diagnostic infrastructure, version strings, code location tracking.

How It Works in Rust

// stringify!: source text as string, not the value
macro_rules! show_expr {
 ($e:expr) => {
     println!("{} = {:?}", stringify!($e), $e)
     //         ^^^^^^^^^^^              ^^
     //         source text of e         evaluated value
 };
}

// Field name โ†’ string (for serialization, reflection, debugging)
macro_rules! field_name {
 ($field:ident) => { stringify!($field) };
}

// Generate test functions with descriptive names
macro_rules! test_case {
 ($name:ident, $input:expr, $expected:expr) => {
     #[test]
     fn $name() {
         assert_eq!($input, $expected,
             concat!("Test '", stringify!($name), "' failed"));
     }
 };
}

// concat!: join literals at compile time โ€” zero allocation
const HELLO: &str = concat!("Hello", ", ", "World", "!");

// Build a version string from multiple sources
const VERSION: &str = concat!("myapp v1.0.0 (", file!(), ")");

// SQL DSL using stringify! for table and column names
macro_rules! select_cols {
 ($f:ident) => { stringify!($f) };
 ($f:ident, $($rest:ident),+) => {
     concat!(stringify!($f), ", ", select_cols!($($rest),+))
 };
}

macro_rules! select {
 ($table:ident . $($col:ident),+) => {
     concat!("SELECT ", select_cols!($($col),+), " FROM ", stringify!($table))
 };
}

// Location info at compile time
macro_rules! here {
 () => { concat!(file!(), ":", line!()) };
}

fn main() {
 // show_expr: source text vs value
 show_expr!(2 + 3 * 4);       // "2 + 3 * 4 = 14"
 show_expr!("hello".len());   // "\"hello\".len() = 5"

 // stringify doesn't evaluate โ€” it captures source text
 let x = 42;
 println!("stringify!(x) = {}", stringify!(x));       // "x" not "42"
 println!("stringify!(1+1) = {}", stringify!(1 + 1)); // "1 + 1" not "2"

 // field names for serialization/display
 println!("Field: {}", field_name!(user_id));      // "user_id"
 println!("Field: {}", field_name!(created_at));   // "created_at"

 // compile-time constants
 println!("{}", HELLO);
 println!("{}", VERSION);

 // SQL DSL
 println!("{}", select!(users.id, name, email));
 // "SELECT id, name, email FROM users"

 println!("Called at: {}", here!());
}

// These generate actual #[test] functions:
test_case!(addition_works, 2 + 2, 4);
test_case!(string_length, "rust".len(), 4);
compile-time string macros quick reference:
MacroReturnsExample
`stringify!($tokens)`Source text as `&str``stringify!(a + b)` โ†’ `"a + b"`
`concat!(...)`Joined literals as `&str``concat!("a", "b")` โ†’ `"ab"`
`file!()`Current file path`"src/main.rs"`
`line!()`Current line number`42usize`
`column!()`Current column`8usize`
`env!("VAR")`Env var at compile time`env!("CARGO_PKG_VERSION")`
`include_str!("f")`File contents as `&str`Contents of file at compile time

What This Unlocks

Key Differences

ConceptOCamlRust
Source location`__FILE__`, `__LINE__`, `__LOC__` โ€” same idea`file!()`, `line!()`, `column!()` โ€” same concept
Identifier โ†’ stringManual string literal `"field_name"``stringify!(field_name)` โ€” guaranteed to match the identifier
String concatenation`"a" ^ "b"` โ€” runtime`concat!("a", "b")` โ€” compile-time, produces a single `&str` constant
Reflection`Obj.field` introspection โ€” runtime`stringify!` โ€” syntax-level, compile-time only
// stringify! and concat! in Rust

// stringify! captures the token text as a string literal
macro_rules! show_expr {
    ($e:expr) => {
        println!("{} = {}", stringify!($e), $e)
    };
}

// Field names from identifiers
macro_rules! field_name {
    ($field:ident) => { stringify!($field) };
}

// Generate test names with concat!
macro_rules! test_case {
    ($name:ident, $input:expr, $expected:expr) => {
        #[test]
        fn $name() {
            assert_eq!($input, $expected,
                concat!("Test '", stringify!($name), "' failed"));
        }
    };
}

// Concatenate at compile time
const VERSION: &str = concat!(
    env!("CARGO_PKG_NAME", "unknown"),
    " v",
    "1.0.0",
    " (",
    file!(),
    ")"
);

// Build SQL-like strings at compile time
macro_rules! select {
    ($table:ident . $($col:ident),+) => {
        concat!(
            "SELECT ",
            concat_fields!($($col),+),
            " FROM ",
            stringify!($table)
        )
    };
}

macro_rules! concat_fields {
    ($f:ident) => { stringify!($f) };
    ($f:ident, $($rest:ident),+) => {
        concat!(stringify!($f), ", ", concat_fields!($($rest),+))
    };
}

// Location macro
macro_rules! here {
    () => {
        concat!(file!(), ":", line!(), ":", column!())
    };
}

fn main() {
    // show_expr
    show_expr!(2 + 3 * 4);
    show_expr!("hello".len());
    show_expr!(vec![1,2,3].len());

    // field names
    println!("Field name: {}", field_name!(user_id));
    println!("Field name: {}", field_name!(created_at));

    // compile-time version string
    println!("Version: {}", VERSION);

    // SQL DSL
    let sql = select!(users.id, name, email);
    println!("SQL: {}", sql);

    // Location
    println!("Called at: {}", here!());

    // stringify vs evaluate
    let x = 42;
    println!("stringify!(x) = {}", stringify!(x));       // "x", not "42"
    println!("stringify!(1+1) = {}", stringify!(1 + 1)); // "1 + 1", not "2"

    // concat! at compile time
    const HELLO_WORLD: &str = concat!("Hello", ", ", "World", "!");
    println!("{}", HELLO_WORLD);
}

test_case!(addition_works, 2 + 2, 4);
test_case!(string_len, "rust".len(), 4);

#[cfg(test)]
mod tests {
    #[test]
    fn test_stringify() {
        assert_eq!(stringify!(my_var), "my_var");
        assert_eq!(stringify!(1 + 1), "1 + 1");
    }

    #[test]
    fn test_concat() {
        const S: &str = concat!("foo", "bar", "baz");
        assert_eq!(S, "foobarbaz");
    }
}
(* stringify! and concat! concepts in OCaml *)

(* OCaml has __MODULE__, __LOC__, __FILE__, __LINE__ built-ins *)

let () =
  Printf.printf "Module: %s\n" __MODULE__;
  Printf.printf "Location: %s\n" __LOC__;
  Printf.printf "File: %s\n" __FILE__;

  (* Compile-time string concatenation via string literals *)
  let greeting = "Hello" ^ ", " ^ "World" ^ "!" in
  Printf.printf "%s\n" greeting;

  (* String of identifier โ€” limited in OCaml *)
  let field_name = "user_id" in  (* manual *)
  Printf.printf "Field: %s\n" field_name