Rust Declarative Macros (`macro_rules!`)
Think of `macro_rules!` as 'find and replace' for your code's structure. It matches patterns at compile time and expands them into boilerplate you don't want to write. It's used for helpers like `vec![]`.
WHY IT EXISTS: Rust needs a way to handle tasks that functions can't, like accepting a variable number of arguments (e.g., println!) or generating code structures at compile time. Declarative macros provide a safe, integrated way to perform this metaprogramming, reducing boilerplate without the dangers of C-style text-substitution macros.
THE MENTAL MODEL: A declarative macro is like a match expression that operates on the structure of your code. You define patterns to look for, and for each pattern, you provide a template of code to generate as a replacement. The compiler handles this expansion before it does its main analysis, effectively letting you teach it new syntax shortcuts.
HOW IT WORKS: You define a macro using the macro_rules! macro itself. Inside, you list one or more rules. Each rule has two parts: a matcher and a transcriber, separated by =>. The matcher describes the syntax of the macro invocation, using metavariables like name:ty to capture a Rust type or e:expr to capture an expression. The transcriber is the code that gets generated, often using the captured metavariables. For example, vec![1, 2] matches a rule that captures a comma-separated list of expressions.
WHEN TO USE IT: Use macro_rules! for DRY (Don't Repeat Yourself) tasks where a function isn't flexible enough. It's perfect for creating simple Domain-Specific Languages (DSLs), like a custom assertion library, or for variadic functions (functions that take a variable number of arguments), like the standard library's vec! and println!.
WHEN NOT TO USE IT: Avoid macros when a simple function or method will suffice. Overusing them can make code harder to read and reason about. For more complex code generation, like creating new trait implementations from struct definitions (e.g., #[derive(Debug)]), you need the more powerful but complex procedural macros.
ONE CANONICAL EXAMPLE: The vec! macro is the classic example. The invocation vec![1, 2, 3] is far more ergonomic than the equivalent manual code: { let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(3); temp_vec }. The macro handles the creation, pushing, and final expression value for you, matching a pattern of ( x:expr ),* to capture any number of expressions.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.