Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 cargo installed).
  • 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:

  1. Declarative macros: defined once, expanded many times
    1. macros 1.0 (macro_rules!): Macros by Example define new syntax in a higher-level, declarative way.
    2. 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.
  2. 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]

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 macro to refer to “macro by example”, which is defined by macro_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 to hello_world![] and hello_world!{}.

Macro Components Explained

macro_rules! <macro_name> {
    (<matcher_of_first_rule>) => {
        <transcriber_of_first_rule>
    };
    
    (matcher_of_second_rule>) => {
        <transcriber_of_second_rule>
    };
    ...
}
  1. Each macro has a name, and one or more rules.
  2. 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.
  3. 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 ident instead of tt.

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

TypeDescriptionExample
blockA block expression (code enclosed in braces {}).{ let x = 5; x }
exprA valid Rust expression. (In Rust 2024, this includes _ and const {}).2 + 2, f(x), const { 1 + 1 }
expr_2021An expression, but excludes _ (UnderscoreExpression) and const {} (ConstBlockExpression).a * b, Some(42)
identAn identifier or a keyword. (Excludes _, raw identifiers like r#foo, and $crate).count, String, match
itemA Rust item (such as a function, struct, trait, or module).fn run() {}, struct User { id: u32 }
lifetimeA lifetime token.'a, 'static
literalA literal value (optionally prefixed with a minus sign -).42, -10.5, "hello", b'a'
metaThe contents (the “meta item”) found inside an attribute like #[...].derive(Debug, Clone), name = "value"
patA pattern. Since Rust 2021, it allows top-level “or-patterns” (using |).Some(x), 1..=5, _, 0 | 1
pat_paramA pattern that does not allow top-level “or-patterns”.Some(x), 42
pathA path (TypePath style).std::io, self::utils::process
stmtA statement. Usually captures without the trailing semicolon.let x = 1, drop(y)
ttA Token Tree. It can be a single token or tokens inside matching (), [], or {}.!, my_var, { 1, 2, 3 }
tyA Rust type.i32, Vec<String>, &'a str
visA 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

#39412

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.

Procedural macros