liveBook · Manning

Procedural macro

As explained in Idiomatic Rust

A procedural macro is a Rust macro implemented as code that programmatically transforms a token stream into generated Rust code.

A procedural macro is Rust code that generates Rust code at compile time. It is useful when repetitive implementations would be tedious to write by hand, and it offers more flexibility than a declarative macro. The common #[derive(...)] syntax is one application. section 8.3 section 5.1

Choose a form according to where the generated code belongs:

  • A function-like macro uses a call such as name!() and can act like a function or code block. section 8.3
  • A derive macro attaches to a struct or enum declaration and can generate code following it. section 8.3
  • An attribute macro attaches to an existing item and can accept arguments. section 8.3

Define the macro in a separate library crate, not a binary crate, and set proc-macro = true under [lib]. Its exported function uses proc_macro::TokenStream for input and output. For a small macro, the output can be produced by parsing a string of Rust syntax. section 8.3

use proc_macro::TokenStream;

#[proc_macro]                     #1
pub fn say_hello_world(_item: TokenStream)       #2
        -> TokenStream {                        #2
    "println!(\"hello world\\")".parse().unwrap()   #3
}

The #[proc_macro] function ignores its input and returns tokens for a println! invocation—the smallest example of code generating code. section 8.3

[lib]
proc-macro = true

This [lib] setting marks the library as a procedural-macro crate. section 8.3

Deriving a trait implementation

A PrintName derive parses its input, handles the type’s generics, and uses quote! to generate a PrintName implementation. The generated name() method returns the struct’s name; the trait’s default print_name() method prints it. Compare the derive invocation with the equivalent handwritten implementation to see what the macro saves. section 8.3

Listing 8.9 Implementing the PrintName derive macro
#[proc_macro_derive(PrintName)]
pub fn print_name(input: TokenStream) -> TokenStream {   #1
    let input = parse_macro_input!(input as DeriveInput);    #1

    let generics = add_trait_bounds(input.generics);         #2
    let (impl_generics, type_generics, where_clause) =   #3
        generics.split_for_impl();          #3            

    let name = input.ident;

    let expanded = quote! {                 #4
        impl #impl_generics print_name::PrintName for #name #type_generics
                #where_clause {
            fn name() -> &'static str {
                stringify!(#name)       #5
            }
        }
    };

    TokenStream::from(expanded)     #6
}

fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
          type_param.bounds.push(                   #7
            parse_quote!(print_name::PrintName)    #7
          );   #7                                     
        }
    }
    generics
}

The derive implementation shows input parsing, generic-parameter handling, quoted Rust syntax, and conversion back to TokenStream. section 8.3

Reach for a procedural macro when generated code earns back its complexity. Macros can reduce repetition, but they can also hide code smells; compiler errors become harder to follow as macros grow more complex. section 5.1 section 8.3

How Idiomatic Rust covers it

How deep it goes
Works through raw token output and a derive implementation with parsed input and generics, but limits treatment to basics.
Examples
Demonstrates a greeting macro and a PrintName derive alongside its generated trait implementation.
What it assumes
Assumes familiarity with Rust crates, traits, and generics while introducing procedural-macro implementation from its basic setup.
What you can do after
Build a basic function-like macro or derive macro in a procedural-macro library crate.
How it connects
Connects procedural macros to declarative macros, traits, generics, and metaprogramming.

Also explained in

Compare how 2 books explain procedural macro →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Idiomatic Rust. The section links point to the parts of the book it draws on.