Macros in Rust are a form of metaprogramming, which means they are code that writes other code. They allow you to write code that expands into more code at compile time, reducing boilerplate, creating Domain-Specific Languages (DSLs), and enabling powerful abstractions.
There are two main types of macros in Rust:
1. Declarative Macros (`macro_rules!`):
These are the most common type and are defined using `macro_rules!`. They operate by pattern matching on Rust syntax trees. You define patterns that the macro should match against and corresponding code templates that the macro should generate. They are often used for creating DSLs, defining repetitive code patterns (like `vec!`), or providing custom syntax for common operations. `macro_rules!` macros are hygienic, meaning they prevent unintended name collisions by ensuring that variables and functions defined within the macro don't clash with names in the calling context.
2. Procedural Macros:
These are more powerful and flexible than declarative macros, allowing for arbitrary Rust code to process and generate syntax. They work with `TokenStream`s, which are sequences of tokens representing Rust code. Procedural macros are implemented as separate crates and come in three forms:
* Function-like macros: Behave like `macro_rules!` but allow arbitrary code processing (e.g., `my_macro!(...)`).
* Derive macros: Automatically implement traits for structs and enums (e.g., `#[derive(Debug)]`). These are very common for implementing standard traits.
* Attribute macros: Allow you to define custom attributes that can be attached to items (e.g., `#[my_attribute] fn my_func() {}`).
Key Characteristics:
* Compile-time Expansion: Macros are expanded into regular Rust code *before* the compiler begins semantic analysis or code generation. This means any errors in macro expansion are caught early.
* DRY Principle: Macros help adhere to the "Don't Repeat Yourself" principle by abstracting away repetitive code patterns.
* Flexibility: They enable highly flexible and expressive APIs.
* Learning Curve: While powerful, writing complex macros (especially procedural ones) can have a steep learning curve due to the need to understand Rust's syntax tree structure.
Macros are identified by an exclamation mark (`!`) at the end of their name when invoked (e.g., `println!`, `vec!`).
Example Code
```rust
// A declarative macro (macro_rules!) to debug print expressions.
// It can take a single expression or a comma-separated list of expressions.
macro_rules! debug_print_expr {
// Rule 1: Matches a single expression.
// `$expr:expr` captures an expression fragment.
($expr:expr) => {
// `stringify!($expr)` converts the expression token tree into a string literal.
// `{:?}` uses the Debug trait for formatting the value.
println!("{}: {:?}", stringify!($expr), $expr);
};
// Rule 2: Matches multiple expressions separated by commas.
// `$($expr:expr),*` is a repetition pattern.
// `$` indicates a macro variable.
// `(...)` groups the pattern.
// `*` indicates zero or more repetitions, separated by `,`.
($($expr:expr),*) => {
// The `$(...)*` in the expansion part repeats the `println!` call
// for each matched expression.
$(
println!("{}: {:?}", stringify!($expr), $expr);
)*
};
}
fn main() {
let a = 5;
let b = 10;
let c = "Rust";
let numbers = vec![100, 200, 300];
println!("--- Single expressions ---");
debug_print_expr!(a); // Expands to: println!("a: {:?}", a);
debug_print_expr!(a + b); // Expands to: println!("a + b: {:?}", a + b);
debug_print_expr!(c.to_uppercase()); // Expands to: println!("c.to_uppercase(): {:?}", c.to_uppercase());
debug_print_expr!(numbers.len()); // Expands to: println!("numbers.len(): {:?}", numbers.len());
println!("\n--- Multiple expressions ---");
debug_print_expr!(a, b, a * b, numbers); // Expands to multiple println! calls
// for a, b, a * b, and numbers respectively.
// Example of a built-in macro: `vec!`
let my_vec = vec![1, 2, 3, 4, 5];
println!("\nBuilt-in vec! macro: {:?}", my_vec);
}
```








Macros