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 $(<matcher | transcriber>)[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

Procedural macros allow you to run custom code during the compilation process. They operate with the same level of access as the compiler itself, including access to standard I/O streams and the file system. Because of this, procedural macros share the same security considerations as Cargo build scripts. However, they are also subject to several unique constraints:

  1. They must be defined in a dedicated crate type specifically marked as a proc-macro crate.
  2. A proc-macro crate can only export procedural macros; it cannot export standard functions or types.
  3. Procedural macros cannot be used within the same crate that defines them.
  4. Defining them requires the compiler-provided proc-macro crate, which is only available within these specialized macro crates.

These constraints can make testing and debugging procedural macros challenging, often creating a barrier for developers who are just starting out.

Fortunately, David Tolnay, a prominent figure in the Rust community, has created several essential crates that simplify this workflow. In this chapter, we will focus on three of them: syn, quote, and proc-macro2. These are the standard tools used in nearly all professional Rust projects for building procedural macros.

By using these crates, we can write our macro logic as standard, testable Rust code. This decouples the core logic from the restricted environment of a proc-macro crate, making it much easier to verify and debug using standard tools.

Instead of jumping straight into the complexities of procedural macro crates, we will begin our journey by learning how to write “code that generates code” within a standard Rust environment.

Let’s go!

TokenStream

TLDR: A procedural macro is a function that takes a (or two for attribute macros) TokenStream as input and returns a TokenStream as output.

Before the compiler calls our procedural macro, it converts the source code (the code to which the macro is applied) into a TokenStream. The compiler then calls our macro with this TokenStream as an argument. Finally, our procedural macro returns a new TokenStream as its result.

A TokenStream is roughly equivalent to a Vec<TokenTree>. A TokenTree is very similar to the tt (Token Tree) metavariable type used in macro_rules!, with only a few minor differences.

See TokenStream in Action

use quote::quote;

fn main() {
    // Convert Rust code to a TokenStream.
    let token_stream = quote! {
        // Comments and whitespace are ignored.

        //! inner doc comment
        // Note: `//! inner doc comment` is parsed as `#![doc = " inner doc comment"]`

        /// doc comment
        // Note: `/// doc comment` is parsed as `#[doc = " doc comment"]`
        fn print_sum(a: i32, b: i32) {
            println!("{}", a + b);
        }
    };

    println!("{}\n", token_stream.to_string());

    for (i, tt) in token_stream.clone().into_iter().enumerate() {
        println!("token {}:", i);
        println!("source code: {}", tt);
        println!("TokenTree: {:?}\n", tt);
    }
}

Tip

We’ll talk about quote! in detail in the quote chapter.

The Procedural Macro Ecosystem: proc-macro2, syn, and quote

Procedural macros function by manipulating the proc_macro::TokenStream type. However, the standard proc_macro crate is a compiler built-in and is restricted to crates explicitly defined as procedural macro libraries. To bypass these limitations and streamline development, the Rust ecosystem relies on three foundational crates:

  • proc-macro2: This crate offers an API almost identical to proc_macro but can be used in any environment (including tests and binaries). It acts as a bridge, facilitating seamless conversion to and from the compiler-native proc_macro::TokenStream.
  • syn: A parsing library that provides a complete syntax tree for any valid Rust source code. It also makes defining custom syntax trees easy.
  • quote: As the counterpart to syn, this crate enables you to convert Rust code templates back into a proc_macro2::TokenStream. Its support for “quasi-quoting” allows you to interpolate variables directly into the generated source code.

Syntax Tree

A syntax tree is a hierarchical representation of source code. It transforms a flat, linear stream of tokens into a structured format that is easy to process and manipulate. The syn crate provides a complete syntax tree that can represent any valid Rust source code. We can use syn to define our own syntax trees; for example, we can define a syntax tree for HTML, CSS, or any other DSL.

A syntax tree is made up of syntax tree nodes. A syntax tree node can be a token, a group of tokens, or any type that implements the syn::parse::Parse trait.

See a Syntax Tree Node in Action

syn::File is a syntax tree (root) node that represents a full source file.

use quote::quote;

fn main() {
    let token_stream = quote! {
        fn main(){
            println!("Hello, world!");
        }
    };

    let syntax_tree: syn::File = syn::parse2(token_stream).unwrap();

    println!("{:#?}", syntax_tree);
}

Tip

Don’t worry if the output seems overwhelming. You don’t need to understand it unless you are working with a full Rust source file.

Furthermore, we won’t be using syn::File in this tutorial.

We will learn how to define our own syntax tree nodes. But first, let’s explore some basic parsing techniques.

Parsing a Single Token

Token!

Token! is a type macro that expands to the Rust type representing a specific token.

use syn::*;

fn main() {
    // Parse the `pub` keyword
    let input = quote::quote! {pub};
    let t: Token![pub] = parse2(input).unwrap();
    println!("{t:?}");
    // Or use parse_quote!
    let t: Token![pub] = parse_quote! {pub};
    println!("{t:?}");
    // Parse the `struct` keyword
    let t: Token![struct] = parse_quote! {struct};
    println!("{t:?}");
    // Parse `+=`
    let t: Token![+=] = parse_quote! {+=};
    println!("{t:?}");
    // Parse `::`
    let t: Token![::] = parse_quote! {::};
    println!("{t:?}");
    // Error: `pub fn main() {}` is not a single token
    // let t: Token![pub] = parse_quote! {pub fn main() {}};
}

custom_keyword!

use syn::*;

// We define custom keywords in a `kw` or `keywords` module by convention.
mod kw{
    syn::custom_keyword!(div);
}

fn main() {
    let t: kw::div = parse_quote! {div};
    println!("{t:?}");
}

Parsing a Syntax Tree Node

use syn::*;

fn main() {
    let node: ItemFn = parse_quote! {fn main() {println!("Hello, world!")}};
    println!("{node:#?}");
    let node: ItemStruct = parse_quote! {struct MyStruct {field: i32}};
    println!("{node:#?}");
    // `syn::DeriveInput` is a syntax tree node that represents any valid input to a derive macro.
    let node: DeriveInput = parse_quote! {#[derive(Debug)] struct MyStruct {field: i32}};
    println!("{node:#?}");
}

Parsing a Custom Syntax Tree Node

There are two ways to parse a custom syntax tree node:

  1. Use a function or closure.
  2. Define a custom syntax tree node type that implements the syn::parse::Parse trait.

Using a function or closure

use quote::*;
use syn::{
    parse::{ParseStream, Parser},
    *,
};
// We define custom keywords in a `kw` or `keywords` module by convention.
mod kw {
    syn::custom_keyword!(div);
}

fn main() {
    let input = quote! {
        // Tip: try modifying it to an invalid div element and see the result.
        <div>"Hello World"</div>
    };
    // parse::Parser::parse2(|input: ParseStream| -> Result<()> { todo!() }, input).unwrap();
    // or
    let parser = |input: ParseStream| -> Result<()> {
        // `ParseStream::parse()` parses a syntax tree node of type `T`,
        // advancing the cursor of the parse stream past it.

        // `<`
        input.parse::<Token![<]>()?;
        // `div`
        input.parse::<kw::div>()?;
        // `>`
        input.parse::<Token![>]>()?;
        // `"Hello World"`
        let str = input.parse::<LitStr>()?;
        // `<`
        input.parse::<Token![<]>()?;
        // `/`
        input.parse::<Token![/]>()?;
        // `div`
        input.parse::<kw::div>()?;
        // `>`
        input.parse::<Token![>]>()?;
        println!("{str:?}");
        println!("Done!");
        Ok(())
    };
    parser.parse2(input).unwrap();
}

Defining a custom syntax tree node type by implementing the syn::parse::Parse trait

struct HtmlNode{...}
impl Parse for HtmlNode{
    fn parse(input: ParseStream) -> Result<Self> {
        todo!()
    }
}
fn main(){
    let node: HtmlNode = parse_quote!{
        <div>"Hello World"</div>
    };
}

Tip

Complex tree nodes (such as syn::File) are composed of simpler tree nodes.

I hope this gives you a clear idea of how to define a custom syntax tree, even for more complex structures.

Rust Syntax Tree

We’ve learned how to implement our own syntax tree nodes for virtually any DSL. But if we only want to work with valid Rust code using syn, we don’t need to implement any custom nodes. The good news is that you don’t even need to learn anything beyond what this tutorial has already covered about syn to handle concrete Rust-code-related tasks.

Here is a simple example to get you started.

Ok Type Extraction

Let’s implement a function that parses a Rust function and extracts the inner success type (Ok type) from its Result return type and prints it to the console.

Tip

You don’t need to read the following implementation. Just run it and observe the output.

use proc_macro2::TokenStream;
use syn::ItemFn;

fn main() {
    ok_type(syn::parse_quote! {fn foo() -> i32 {}});
    ok_type(syn::parse_quote! {fn foo() -> Result<i32> {}});
    ok_type(syn::parse_quote! {fn add(a:u32,b:u32) -> Result<u32, Error> {}});
}

fn ok_type(item_fn: TokenStream) {
    println!(r#"fn: "{}""#, item_fn.to_string());
    let item_fn: ItemFn = syn::parse_quote! {#item_fn};
    let syn::ReturnType::Type(_, return_type) = item_fn.sig.output else {
        println!("Ok Type Unknown.");
        return;
    };
    let syn::Type::Path(return_type) = return_type.as_ref() else {
        println!("Ok Type Unknown.");
        return;
    };
    let syn::PathArguments::AngleBracketed(generic_args) =
        &return_type.path.segments.first().unwrap().arguments
    else {
        println!("Ok Type Unknown.");
        return;
    };
    let syn::GenericArgument::Type(syn::Type::Path(ok_type)) = generic_args.args.first().unwrap()
    else {
        println!("Ok Type Unknown.");
        return;
    };
    println!("Ok type is: {}", ok_type.path.get_ident().unwrap());
}

The above code uses a few syn nodes. How do you know in advance which nodes you need to implement your own ok_type function? The answer is: no, you don’t need to know anything.

You just need to start at the very top node — ItemFn for this example. Print its debug output, then figure out the “happy path” that leads to your task target.

For example:

use syn::ItemFn;

fn main() {
    let item_fn: ItemFn = syn::parse_quote! {fn foo() -> Result<i32> {}};
    println!("{item_fn:#?}");
}

Then follow the “happy path” all the way to your target — i32 in this example. The golden rule is: focus only on the “happy-path”.

Spans

When parsing malformed input, simply stating what is wrong without indicating its location is not very helpful for debugging.

To provide precise error locations, we use Spans. A Span is an opaque value representing a specific range of source code. While they cannot be modified, they can be created or retrieved. Their primary purpose is error reporting, and every token carries an associated Span.

Example with Coarse-grained Spans

use syn::{
    parse::{Parse, ParseStream},
    *,
};

struct HtmlNode;
impl Parse for HtmlNode {
    fn parse(input: ParseStream) -> Result<Self> {
        input.parse::<Token![<]>()?;
        input.parse::<Ident>()?;
        input.parse::<Token![>]>()?;
        input.parse::<LitStr>()?;
        input.parse::<Token![<]>()?;
        input.parse::<Token![/]>()?;
        input.parse::<Ident>()?;
        input.parse::<Token![>]>()?;
        Ok(HtmlNode)
    }
}
fn main() {
    // `quote!` assigns the same span to all tokens inside the block.
    let input = quote::quote! {<div>"Hello World"<div>};
    if let Err(e) = syn::parse2::<HtmlNode>(input) {
        println!("Error: {} at {:?}", e, e.span());
    }
}

Example with Precise Spans

use std::str::FromStr;

use proc_macro2::TokenStream;
use syn::{
    parse::{Parse, ParseStream},
    *,
};

struct HtmlNode;
impl Parse for HtmlNode {
    fn parse(input: ParseStream) -> Result<Self> {
        input.parse::<Token![<]>()?;
        input.parse::<Ident>()?;
        input.parse::<Token![>]>()?;
        input.parse::<LitStr>()?;
        input.parse::<Token![<]>()?;
        input.parse::<Token![/]>()?;
        input.parse::<Ident>()?;
        input.parse::<Token![>]>()?;
        Ok(HtmlNode)
    }
}
fn main() {
    // `TokenStream::from_str` assigns a unique span to each individual token.
    let input = TokenStream::from_str(r#"<div>"Hello World"<div>"#).unwrap();
    if let Err(e) = syn::parse2::<HtmlNode>(input) {
        println!("Error: {} at {:?}", e, e.span());
    }
}

The quote Crate

We have discussed how to parse input using syn. Now, it’s time to generate output with quote.

quote!

The quote! macro is a procedural macro that takes a template of Rust code and returns a TokenStream. Its usage is similar to macro_rules!.

Macro NameInterpolationInterpolation TypeRepetition
macro_rules!$varmetavariable$(<...>)[delimiter]<*|?|+>
quote!#varany type implementing ToTokens#(<...>)[delimiter]*
fn main() {
    let f: syn::ItemFn = syn::parse_quote!(
        fn foo(x: i32) -> i32 {
            println!("Hello World");
        }
    );
    let fn_name = f.sig.ident;
    let fn_input = f.sig.inputs;
    let fn_output = f.sig.output;
    let fn_block = f.block;
    // Convert the function to be public
    let token_stream = quote::quote! {
        pub fn #fn_name (#fn_input) #fn_output #fn_block
    };
    println!("{}", token_stream);
}

Procedural Macro Crates

Congratulations, fellow Rustacean🦀! You’ve reached a major milestone. Now, you’re ready to learn the final, essential step: how to package and use your procedural macros in a crate.

Creating a Proc-Macro Crate

A dedicated library crate is mandatory for procedural macros because they must have the proc-macro crate type enabled. Crucially, they cannot be defined within the same crate that uses them.

To create one, run:

cargo new --lib my_proc_macros
cd my_proc_macros
cargo add syn quote proc-macro2

Then, update your Cargo.toml to enable the proc-macro feature:

[lib]
proc-macro = true

Function-like Procedural Macros

Function-like macros are called with a following exclamation mark (like println!).

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident};

// In `src/lib.rs`, define a function-like procedural macro.
#[proc_macro]
pub fn hello_macro(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syn Ident
    let name = parse_macro_input!(input as Ident);
    let name_str = name.to_string();

    let output = quote! {
        println!("Hello, {}!", #name_str);
    };

    TokenStream::from(output)
}

We can now use hello_macro in a normal crate:

use my_proc_macros::hello_macro;

fn main() {
    hello_macro!(world);
}

The input Parameter

The input contains the tokens enclosed by whatever delimiters (parentheses (), brackets [], or braces {}) are used when calling the macro. In the example above, the input is world. Upon expansion, the call hello_macro!(world) is replaced entirely by the macro’s output.

The parse_macro_input! Macro

The parse_macro_input! macro parses the input TokenStream into a specific syn syntax tree node (including custom syntax tree nodes). If parsing fails, it automatically generates a high-quality compile-time error.

The basic syntax is parse_macro_input!(<TokenStream> as <Type>). This convenience macro is specifically designed for use within proc-macro crates.

Attribute Procedural Macros

Attribute macros define custom attributes that can be attached to items like functions or structs.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

// In `src/lib.rs`, define an attribute procedural macro.
#[proc_macro_attribute]
pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // We parse the item (e.g., a function) the attribute is attached to
    let input_item = parse_macro_input!(item as ItemFn);

    // We keep the original item as-is
    let output = quote! {
        #input_item
    };

    TokenStream::from(output)
}

We can use my_attribute in a normal crate:

use my_proc_macros::my_attribute;

#[my_attribute(attr1, attr2, key=value)]
pub fn foo() {
    println!("Hello from foo!");
}

Parameters in Attribute Macros

  • attr: The tokens inside the attribute’s parentheses (e.g., attr1, attr2).
  • item: The tokens for the item the attribute is attached to (e.g., a function, struct, or enum).

Unlike function-like macros, which replace the call itself, an attribute macro replaces the entire item it is attached to with its output.

Derive Procedural Macros

Derive macros create new items (usually trait implementations) for an existing item.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyDerive, attributes(my_helper))]
pub fn my_derive(input: TokenStream) -> TokenStream {
    // Parse the entire struct/enum/union
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let output = quote! {
        impl MyTrait for #name {
            fn hello() {
                println!("Hello from my derive!");
            }
        }
    };

    TokenStream::from(output)
}

We can use MyDerive in a normal crate:

use my_proc_macros::MyDerive;

#[derive(MyDerive)]
pub struct MyStruct {
    #[my_helper]
    pub field1: i32,
}

The input Parameter

The input TokenStream represents the entire item (struct, enum, or union) that the #[derive(...)] attribute is decorating.

Append-only Expansion

Unlike attribute macros, a derive macro’s output does not replace the input item. Instead, the output (usually an implementation block) is appended to the module or block containing the original item.


Happy macro programming! 🦀