418: `stringify!` and `concat!` Macros
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
stringify!($expr) captures the token representation of an expressionconcat! joins multiple string literals and stringify! outputs into a single &'static strstring_enum! uses stringify! to generate as_str() methods automaticallystringify!(1 + 2) produces "1 + 2", not "3"Code Example
let name = stringify!(my_variable); // "my_variable"
let expr = stringify!(x + y * z); // "x + y * z"Key Differences
stringify! captures token text without evaluation; OCaml has no equivalent without PPX.concat! joins at compile time; OCaml uses ^ (runtime) or Printf.sprintf (runtime).file!() / __FILE__ and line!() / __LINE__; Rust's are macros, OCaml's are special values.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);
}
}#[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.**Exercises
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.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.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.