liveBook · Manning

Macro

As explained in Idiomatic Rust

A macro is a named construct that expands or transforms source code, commands, or templates before or during execution.

A macro generates Rust code from code supplied at its call site. It is useful when a function cannot conveniently express the desired syntax or when many definitions repeat the same pattern. Declarative macros use `macro_rules!`; procedural macros are a separate, more complex system. section 5.1 section 5.1.2

A call such as `println!("hello")` has a mandatory `!`, distinguishing it from a function call. A `macro_rules!` definition contains matcher-and-expansion arms: `macro_rules! noop_macro { () => {}; }` matches an empty call. Matchers inspect Rust code fragments, not the results of executing that code. For example, `$e:expr` matches an expression and `$s:stmt` matches a statement; a call matching no arm produces a compiler error. section 5.1.1

Forward a variable number of tokens

`macro_rules! special_println { ($($arg:tt)*) => { println!($($arg)*) }; }` forwards its input to `println!`. Here `tt` matches token trees, `$(…)*` allows zero or more repetitions, and the expansion repeats the captured input. This is a practical reason to choose a macro over a function: the call can accept variadic arguments. section 5.1.2

macro_rules! var_print {
    ($($v:ident),*) => {     #1
        println!(
         concat!($(stringify!($v),"={:?} "),*), $($v),*     #2
        )
    };
}

The `var_print!` listing matches comma-separated identifiers and expands them into printed names and debug-formatted values, showing how repetition can generate both labels and arguments. section 5.1.2

Macros can also provide a small domain-specific language. `lazy_static!` matches declarations shaped like `static ref NAME: TYPE = EXPR;`, forwards them to an internal macro, and recursively processes the remaining declarations until an empty matcher ends the expansion. Keep such syntax simple enough to follow. section 5.1.3

Listing 5.5 Adding with_str! and with! macros to BicycleBuilder
macro_rules! with_str {
    ($name:ident, $func:ident) => {     #1
        fn $func(&mut self, $name: &str) {
            self.bicycle.$name = $name.into()     #2
        }
    };
}

macro_rules! with {
    ($name:ident, $func:ident, $type:ty) => {    #3
        fn $func(&mut self, $name: $type) {
            self.bicycle.$name = $name
        }
    };
}

impl BicycleBuilder {
    with_str!(make, with_make);
    with_str!(model, with_model);
    with!(size, with_size, i32);
    with_str!(color, with_color);
}

The builder listing replaces repeated methods with two small macros: `with_str!` accepts `&str` and stores a `String`, while `with!` accepts a specified field type. Separate macros keep the difference explicit. section 5.3.3

Use macros to remove genuinely repetitive code, not to hide unclear design. For builders, an existing solution such as `derive_builder` may save more work than maintaining custom macros. section 5.1 section 5.3.3

How Idiomatic Rust covers it

How deep it goes
Works through declarative matcher fragments, repetition, expansion and builder methods; defers procedural macros.
Examples
Uses `println!` wrappers, identifier-printing macros, `lazy_static!` declarations and generated builder methods.
What it assumes
Builds on Rust generics, traits and pattern matching; introduces basic macro syntax before using macros in design patterns.
What you can do after
Build it from scratch with `macro_rules!` for variadic calls or repeated methods, while judging when an existing crate is preferable.
How it connects
Connects metaprogramming and domain-specific languages with token trees, functions and the builder pattern.

Also explained in

Compare how 9 books explain 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.