Introduction
Welcome Rustacean! Let’s learn about Rust macros.
Prerequisites
To get the most out of this tutorial, we recommend having:
- Familiarity with basic Rust syntax (variables, functions, and structs).
- A functional Rust development environment (with
cargoinstalled). - A solid understanding of Rust’s core concepts, such as the type system and ownership.
If you’re new to Rust, consider completing the official Rust Book first.
What are Macros
Simply put, macros are a metaprogramming language that allows us to write code that generates code.
They are especially useful when you need:
- Repeatable code patterns
- Domain-specific abstractions
- Compile-time computations
Types of Macros
Rust has two main kinds of macros:
- Declarative macros: defined once, expanded many times
- macros 1.0 (
macro_rules!): Macros by Example define new syntax in a higher-level, declarative way. - macros 2.0: “Macros 2.0 #39412” is a general term for a long-running, experimental effort within the Rust project to create a unified and improved macro system to replace the original
macro_rules!system. It is not a feature currently available on stable Rust.
- macros 1.0 (
- Procedural macros: Procedural macros allow creating syntax extensions as execution of a function. Procedural macros come in one of three flavors:
- Function-like macros -
custom!(...) - Derive macros -
#[derive(CustomDerive)] - Attribute macros -
#[CustomAttribute]
- Function-like macros -
What You’ll Learn
Here’s what you’ll learn in this tutorial:
- How to write your own macros
- Best practices for macro safety and maintainability
- Common macro patterns in the Rust ecosystem
How to Use This Book
This tutorial is designed to be interactive and hands-on. Here’s how to get the most out of it:
Reading Order
For the best learning experience, we recommend reading this tutorial in order. While you are free to explore different sections, macros build on concepts progressively; following the intended sequence will help you understand the material more effectively.
Interactive Code Blocks
This book contains many runnable and editable Rust code blocks:
- Runnable: Click the play button at the top-right of any code block to execute it and see the output directly.
- Editable: Edit the code directly in the editor, then click the play button to re-run the updated code with your changes.
Tips for Learning
- Try modifying the examples to understand how macros work
- Use the button to verify your changes compile and behave as expected
- Don’t hesitate to break things - the tutorial is designed for experimentation
Macros 1.0: macro_rules!
macro_rules! is a special macro that allows us to define our own macros in a declarative way. We call such extensions “macros by example” or simply “macros”.
Note
We will use
macroto refer to “macro by example”, which is defined bymacro_rules!throughout this chapter.
Hello World
fn main(){
macro_rules! hello_world {
() => {
println!("Hello, world!");
};
}
hello_world!();
}
Note
With
macro_rules!, you can use(),[], or{}interchangeably in both the macro declaration and the macro call. For example,hello_world!()is equivalent tohello_world![]andhello_world!{}.
Macro Components Explained
macro_rules! <macro_name> {
(<matcher_of_first_rule>) => {
<transcriber_of_first_rule>
};
(matcher_of_second_rule>) => {
<transcriber_of_second_rule>
};
...
}
- Each macro has a name, and one or more rules.
- Each rule has two parts:
- a matcher, describing the syntax that it matches
- a transcriber, describing the syntax that will replace a successfully matched invocation.
- Macros can expand to expressions, statements, items (including traits, impls, and foreign items), types, or patterns.
Multi-rules Example
macro_rules! echo{
(Hello World) => {
println!("Rule 1: Hello World");
};
(Hello) => {
println!("Rule 2: Hello");
};
// use a metavariable to catch all
// we will introduce metavariables later
($($name:tt)*) => {
print!("Rule 3: ");
$(
print!("{}", stringify!($name));
)*
println!();
};
}
fn main(){
echo!(Hello World);
echo!(Hello);
echo!(3 + 2 = 5!);
echo!(1,2,3);
}
Metavariables
Without metavariables, a rule can only match literal values exactly.
A First Look at Metavariables
macro_rules! print{
($x:tt) => { println!("{}", $x); };
}
fn main() {
print!(1);
let v="Hello, world!";
print!(v);
}
$x:tt is a metavariable named x with the type tt (token tree).
Note
You may find that other resources use the term “fragment specifier” to describe metavariable types.
tt (Token Tree): Matches a single token or multiple tokens within matching delimiters (), [], or {}.
While there are many other metavariable types, the tt type is the most flexible. You can think of it as a “catch-all” metavariable type.
Building a DSL Example
macro_rules! SQL{
// `expr` matches an expression.
(SELECT $e:expr;)=>{
println!("Value: {}", $e);
};
// `ident` matches a single identifier.
(SELECT * FROM $t:ident)=>{
println!("Table: {}", $t);
};
// Catch-all case. This uses repetition, which will be introduced in the next chapter.
($($t:tt)*) => {
print!("Unknown: ");
$(
print!("{} ", stringify!($t));
)*
println!();
};
}
fn main(){
SQL!(SELECT (3+2*4););
let user_table="USER_TABLE";
SQL!(SELECT * FROM user_table);
SQL!(SELECT 1+1 AS total FROM "USER_TABLE" WHERE id > 10);
}
Tip
Always use the most specific metavariable type possible. For example, if you only need to match a single identifier, use
identinstead oftt.
The Built-in $crate Metavariable
$crate is a built-in metavariable that always expands to the name of the crate being compiled.
mod utils{
pub const message: &'static str="Hello, world!";
}
macro_rules! greet{
() => {
println!("{}!", $crate::utils::message);
};
}
fn main(){
greet!();
}
Metavariable Types
| Type | Description | Example |
|---|---|---|
block | A block expression (code enclosed in braces {}). | { let x = 5; x } |
expr | A valid Rust expression. (In Rust 2024, this includes _ and const {}). | 2 + 2, f(x), const { 1 + 1 } |
expr_2021 | An expression, but excludes _ (UnderscoreExpression) and const {} (ConstBlockExpression). | a * b, Some(42) |
ident | An identifier or a keyword. (Excludes _, raw identifiers like r#foo, and $crate). | count, String, match |
item | A Rust item (such as a function, struct, trait, or module). | fn run() {}, struct User { id: u32 } |
lifetime | A lifetime token. | 'a, 'static |
literal | A literal value (optionally prefixed with a minus sign -). | 42, -10.5, "hello", b'a' |
meta | The contents (the “meta item”) found inside an attribute like #[...]. | derive(Debug, Clone), name = "value" |
pat | A pattern. Since Rust 2021, it allows top-level “or-patterns” (using |). | Some(x), 1..=5, _, 0 | 1 |
pat_param | A pattern that does not allow top-level “or-patterns”. | Some(x), 42 |
path | A path (TypePath style). | std::io, self::utils::process |
stmt | A statement. Usually captures without the trailing semicolon. | let x = 1, drop(y) |
tt | A Token Tree. It can be a single token or tokens inside matching (), [], or {}. | !, my_var, { 1, 2, 3 } |
ty | A Rust type. | i32, Vec<String>, &'a str |
vis | A visibility qualifier (this can be empty). | pub, pub(crate) |
Repetitions
We use $(<metavariable>)[delimiter]<*|?|+> to specify metavariable repetition in both the pattern (matcher) and the expansion (transcriber).
*: Zero or more times?: Zero or one time+: One or more times
Optional Repetitions (?)
macro_rules! greet{
($($msg:literal)?) => {
let msg = concat!("Hello, world! ", $($msg)?);
println!("{}", msg);
}
}
fn main(){
greet!();
greet!("Hello, Rustacean! 👋");
}
Zero or More Repetitions (*)
macro_rules! sum{
($($n:expr)*) => {
let mut sum=0;
$(sum += $n;)*
println!("The sum is {}", sum);
}
}
fn main(){
sum!(1 2 3);
}
Repetitions with Delimiters
macro_rules! sum{
($($n:expr),*) => {
let mut sum=0;
$(sum += $n;)*
println!("The sum is {}", sum);
}
}
fn main(){
sum!(1, 2, 3);
}
Custom Delimiter Tokens
macro_rules! sum{
($($n:literal)add*) => {
let mut sum=0;
$(sum += $n;)*
println!("The sum is {}", sum);
}
}
fn main(){
sum!(1 add 2 add 3);
}
Tip
The delimiter token can be any token other than a delimiter or one of the repetition operators, but
;and,are the most common.
Scoping
Textual scope
fn main(){
// m!{}; // Error: macro `m` is not defined in this scope
// new scope
{
macro_rules! m{
() => {
println!("Hello, world!");
}
}
m!{}; // OK
// shadowing
macro_rules! m{
() => {
println!("Hello, Rustacean!");
}
}
m!{}; // OK
}
// m!{}; // Error: macro `m` is not defined in this scope
}
Tip
Textual scope works similarly to the scope of local variables declared with
let.
Path-based Scope
mod mod1{
macro_rules! m{
() => {
println!("Hello, world!");
}
}
}
mod mod2{
macro_rules! m{
() => {
println!("Hello, Rustacean!");
}
}
pub(crate) use m; // re-export to gain path-based scope
}
fn main(){
// mod1::m!{}; // Error: By default, a macro has no path-based scope.
mod2::m!{}; // OK: `m` is re-exported from `mod2`.
}
Exporting Macros
mod mod_level_1{
mod mod_level_2{
// By default, a macro is implicitly `pub(crate)`
// `#[macro_export]` makes it `pub` and export it to the root of the crate.
#[macro_export]
macro_rules! m{
() => {
println!("Hello, world!");
}
}
}
}
fn main(){
// mod_level_1::m!{}; // Error: `m` is not in scope.
// mod_level_1::mod_level_2::m!{}; // Error: `m` is not in scope.
crate::m!{}; // OK
}
Hygiene
fn func() {
println!("Hello, world! (from definition site)");
}
fn main(){
let x = 1;
macro_rules! m {
() => {
println!("x = {}", x); // Uses `x` from the definition site.
func(); // Uses `func` from the invocation site.
$crate::func(); // meta-variable `$crate` refers to the crate where the macro is defined.
};
}
{
let x = 2;
fn func() {
println!("Hello, Rustacean! (from invocation site)");
}
m!();
}
}
Macros 2.0
Note
Macros 2.0 is a proposal to improve the macros 1.0 (declarative macros
macro_rules!). It is not yet implemented.
Why Macros 2.0?
Macros 1.0 has several limitations:
- Lack of hygiene: Macros 1.0 can capture variables from the surrounding scope, which can lead to unexpected behavior.
- Lack of modularity: Macros 1.0 cannot be easily composed or reused.
- Lack of type safety: Macros 1.0 cannot be easily checked for type safety.
Macros 2.0 aims to address these limitations by providing a more powerful and flexible macro system.
Note
We will get it back when it is implemented.
Tip
We actually don’t need macros 1.0 or macros 2.0, if we can use procedural macros.