Macros
Declarative Macros
A declarative macro ("macro by example", defined with macro_rules!) transforms syntax by matching input tokens against one or more pattern rules and substituting the matched fragments into a template transcriber, operating much like a compile-time match expression over syntax trees.
Anatomy of a Declarative Macro
macro_rules! macro_name {
( pattern_1 ) => { expansion_1 };
( pattern_2 ) => { expansion_2 };
}
^^^^^^^^^ ^^^^^^^^^^^
Matcher Transcriber
- Matcher: Describes the expected token structure and captures syntax fragments into metavariables (
$name:designator, such as$x:expror$t:ty). - Transcriber: Emits the replacement Rust code, substituting
$namewherever it appears. - Top-down rule evaluation: The compiler tries each arm from top to bottom and expands the first arm whose matcher matches the input tokens completely.
Invoking a Declarative Macro with (), [], or {}
Every function-like macro in Rust can be invoked using parentheses (...), square brackets [...], or curly braces {...}:
my_macro!(...)andmy_macro![...]require a trailing semicolon;when used as a statement.my_macro! { ... }can omit the trailing semicolon when used as an item or statement block.
macro_rules! map_pair {
($k:expr => $v:expr) => {
($k, $v)
};
}
fn main() {
// All three delimiter styles are valid and equivalent:
let a = map_pair!("host" => "localhost");
let b = map_pair!["port" => 8080];
let c = map_pair! { "tls" => true };
println!("a = {a:?}, b = {b:?}, c = {c:?}");
}
a = ("host", "localhost"), b = ("port", 8080), c = ("tls", true)
Context-Sensitive Expansion (Expressions, Statements, Items, Patterns)
Depending on where a declarative macro is invoked in source code, its transcriber can expand into:
- An expression:
let x = vec![1, 2, 3]; - One or more statements:
println!("hi"); - Module-level items:
struct,enum,fn, orimplblocks - A type or pattern: inside a type annotation or
matcharm
// Expands into a module-level newtype struct and a constructor method:
macro_rules! define_id_type {
($name:ident) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct $name(u64);
impl $name {
const fn new(raw: u64) -> Self {
Self(raw)
}
}
};
}
define_id_type!(OrderId);
define_id_type!(CustomerId);
fn main() {
let order = OrderId::new(1001);
let customer = CustomerId::new(1001);
// `OrderId` and `CustomerId` are distinct types that cannot be mixed up:
println!("order = {order:?}, customer = {customer:?}");
}
order = OrderId(1001), customer = CustomerId(1001)