Foreword
Written for the Rust Auckland meetup 2019-12-02.
- Rust Auckland meetup: https://www.meetup.com/rust-akl/
- Slack: https://rust-akl.slack.com/messages/CCC7KUXMY/
- Repository: https://github.com/azriel91/rust_macros
Feel free to use and improve on these.
What Are (Rust) Macros
(not azriel's) Definition: Copy-paste code without the pasta. And varargs.
- 
Shortcode: Original: #![allow(unused_variables)] fn main() { use std::ops::Add; #[derive(Clone, Copy, PartialEq)] pub struct HealthPoints(pub u32); impl Add for HealthPoints { type Output = HealthPoints; fn add(self, other: HealthPoints) -> HealthPoints { HealthPoints(self.0 + other.0) } } }Macro-treated: #[derive(derive_more::Add, Clone, Copy, PartialEq)] pub struct HealthPoints(pub u32);
- 
Varargs: #![allow(unused_variables)] fn main() { let (a, b) = (3., 2.); println!(); println!("hello"); println!( "{a:.1} รท {b:.1} = {answer:.2}", a = a, b = b, answer = a / b ); }
Why Macros
Mainly to increase development ergonomics:
- ๐ฅ Reduce duplication.
- ๐๏ธ Reduce boilerplate.
- ๐ Varargs.
Not because you can do this
macro_rules! java { (static void $name:ident($($_:tt)+) { $($body:tt)+ }) => { fn $name() { java!($($body)+); } }; ($_:ident.$__:ident.$fn_name:ident($args:tt);) => { println!($args); }; } java! { static void main(String[] args) { System.out.println("jRust!"); } } // Need to do this to get playpen to detect main function. #[cfg(test)] fn main() {}
๐ฅ Reduce duplication
Scenario: Writing similar syntax.
Without macro
#[test]
fn on_start_delegates_to_state() {
    let (mut state, mut world, invocations): (RobotState<(), ()>, _, _) =
        setup_without_intercepts();
    state.on_start(StateData::new(&mut world, &mut ()));
    assert_eq!(vec![Invocation::OnStart], *invocations.borrow());
}
#[test]
fn on_stop_delegates_to_state() {
    let (mut state, mut world, invocations): (RobotState<(), ()>, _, _) =
        setup_without_intercepts();
    state.on_stop(StateData::new(&mut world, &mut ()));
    assert_eq!(vec![Invocation::OnStop], *invocations.borrow());
}
#[test]
fn on_pause_delegates_to_state() {
    let (mut state, mut world, invocations): (RobotState<(), ()>, _, _) =
        setup_without_intercepts();
    state.on_pause(StateData::new(&mut world, &mut ()));
    assert_eq!(vec![Invocation::OnPause], *invocations.borrow());
}
// ..
With macro
#[macro_use]
macro_rules! delegate_test {
    ($test_name:ident, $function:ident, $invocation:expr) => {
        #[test]
        fn $test_name() {
            let (mut state, mut world, invocations): (RobotState<(), ()>, _, _) =
                setup_without_intercepts();
            state.$function(StateData::new(&mut world, &mut ()));
            assert_eq!(vec![$invocation], *invocations.borrow());
        }
    };
}
delegate_test!(on_start_delegates_to_state, on_start, Invocation::OnStart);
delegate_test!(on_stop_delegates_to_state, on_stop, Invocation::OnStop);
delegate_test!(on_pause_delegates_to_state, on_pause, Invocation::OnPause);
// ..
๐๏ธ Reduce boilerplate
Scenario: Newtype that behaves like a number -- math operators work as any numeric type.
/// Good programmer: Strongly typed instead of Stringly typed.
pub struct HealthPoints(pub u32);
let mut me = HealthPoints(99);
let heal = HealthPoints(1);
// Want: 100 health
me.0 = me.0 + heal.0; // Not ergonomic
me = me + heal;
// me = me - heal;
// me += heal;
// me -= heal;
No macros
#![allow(unused_variables)] fn main() { use std::ops::{Add, AddAssign, Sub, SubAssign}; /// Character health points. #[derive(Clone, Copy, PartialEq)] pub struct HealthPoints(pub u32); impl Add for HealthPoints { type Output = HealthPoints; fn add(self, other: HealthPoints) -> HealthPoints { HealthPoints(self.0 + other.0) } } impl AddAssign for HealthPoints { fn add_assign(&mut self, other: HealthPoints) { *self = HealthPoints(self.0 + other.0); } } impl Sub for HealthPoints { type Output = HealthPoints; fn sub(self, other: HealthPoints) -> HealthPoints { HealthPoints(self.0 - other.0) } } impl SubAssign for HealthPoints { fn sub_assign(&mut self, other: HealthPoints) { *self = HealthPoints(self.0 - other.0); } } }
Reduction level 1 -- `proc_macro_derive`
/// Character health points.
#[derive(
    derive_more::Add,
    derive_more::AddAssign,
    derive_more::Sub,
    derive_more::SubAssign,
    derive_more::Display,
    derive_more::From,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
)]
pub struct HealthPoints(pub u32);
/// Character skill points.
#[derive(
    derive_more::Add,
    derive_more::AddAssign,
    derive_more::Sub,
    derive_more::SubAssign,
    derive_more::Display,
    derive_more::From,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
)]
pub struct SkillPoints(pub u32);
Reduction level 2 -- `proc_macro_attribute`
use numeric_newtype_derive::numeric_newtype;
/// Character health points.
#[numeric_newtype]
pub struct HealthPoints(pub u32);
/// Character skill points.
#[numeric_newtype]
pub struct SkillPoints(pub u32);
๐ Varargs
Rust doesn't support function overloading.
// Not supported:
fn my_println<A>(format: &'static str, a: A) -> String { /* .. */ }
fn my_println<A, B>(format: &'static str, a: A, b: B) -> String { /* .. */ }
fn my_println<A, B, C>(format: &'static str, a: A, b: B, c: C) -> String { /* .. */ }
Instead, macros can be used to generate a function body:
#![allow(unused_variables)] fn main() { macro_rules! my_println { ($($token:tt),*) => { $( println!("{}", $token); )* }; } my_println!("hello", "world"); my_println!("hello", "rust", "akl"); // Short for writing: // // println!("{}", "hello"); // println!("{}", "world"); // println!("{}", "hello"); // println!("{}", "rust"); // println!("{}", "akl"); }
Macro Types
Macros come in multiple forms:
- 
Declarative 
- 
Procedural - Function-like
- Derive
- Attribute
 
Declarative
Human: "Macro, here are some (well-formed) tokens."
Macro: "Here are some code tokens." ย ย OR
Macro: "No rules expected the token `..`"
Definition
#![allow(unused_variables)] fn main() { macro_rules! my_macro { (pattern_0) => { println!("some tokens"); }; (pattern_1) => { println!("other tokens"); }; // $param_name:param_type // $( $repeated_param:param_type ),* // Zero or more, comma delimited // $( $repeated_param:param_type ),+ // One or more, comma delimited // // See <https://danielkeep.github.io/tlborm/book/mbe-macro-rules.html#captures> ($name:ident) => { println!("{}", stringify!($name)); }; } }
Usage
#![allow(unused_variables)] fn main() { macro_rules! my_macro { (pattern_0) => { println!("some tokens"); }; (pattern_1) => { println!("other tokens"); }; ($name:ident) => { println!("ident: {}", stringify!($name)); }; (pattern_2) => { println!("even more tokens"); }; } my_macro!(pattern_0); my_macro!(pattern_1); my_macro!(single_identifier); my_macro!(pattern_2); // note: rules are evaluated in order. }
Declarative macros in the wild
- 
#[macro_use] extern crate bitflags; bitflags! { struct Flags: u32 { const A = 0b00000001; const B = 0b00000010; const C = 0b00000100; const ABC = Self::A.bits | Self::B.bits | Self::C.bits; } }
- 
#[macro_use] extern crate lazy_static; use std::collections::HashMap; lazy_static! { static ref HASHMAP: HashMap<u32, &'static str> = { let mut m = HashMap::new(); m.insert(0, "foo"); m.insert(1, "bar"); m.insert(2, "baz"); m }; static ref COUNT: usize = HASHMAP.len(); static ref NUMBER: u32 = times_two(21); } fn times_two(n: u32) -> u32 { n * 2 } fn main() { println!("The map has {} entries.", *COUNT); println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap()); println!("A expensive calculation on a static results in: {}.", *NUMBER); }
See also: The Little Book of Rust Macros (comprehensive guide)
Procedural
Differences from declarative macros:
- ๐ฒ Parsing AST instead of matching patterns.
- ๐บ Can write procedural logic.
- ๐ฆ Better diagnostics.
- ๐ฏ Easier to test.
- ๐ฆ Dedicated crate (not a selling point).
Types:
- Function-like
- Derive
- Attribute
Function-Like
- Takes in any well-formed tokens.
- Outputs replacement tokens.
function_like!("Looks just like `macro_rules!`");
function_like! {
    struct Name {
        field: Type
    }
}
my_vec![];
map! {
    "key_0" => 123,
    "key_1" => 456,
};
#![allow(unused_variables)] fn main() { println!("hi"); println! { "hi" }; println!["hi"]; let numbers_0 = vec! { 1, 2, 3 }; let numbers_1 = vec!(1, 2, 3); println!("{:?}", numbers_0); println! { "{:?}", numbers_1 }; }
Derive
- Attached to a struct / enum.
- Generates additional tokens.
- Can have helper attributes.
- Cannot see sibling derives.
#[derive(CustomDerive)]
struct Struct; // Can see this.
// Can't see this.
impl Struct {
    // ..
}
#[derive(Clone, Copy, a_crate::CustomDerive)]
#[custom_derive(Debug)] // type level helper attribute
enum Enum {
    #[custom_derive(skip = "true")] // field level helper attribute
    Variant0,
    Variant1 { value: u32 },
}
This is not reflection!
Attribute
- See everything about an item.
- Takes in tokens, and outputs replacement tokens.
// See <https://github.com/azriel91/proc_macro_roids>.
use macro_crate::append_cd;
#[append_cd]
struct StructNamed { a: u32, b: i32 }
// outputs:
struct StructNamed { a: u32, b: i32, c: i64, d: usize }
๐ Rocket framework:
#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}
fn main() {
    rocket::ignite().mount("/", routes![hello]).launch();
}
Debugging
Panic
For proc-macros, well placed panic!()s are immensely useful:
let token_stream = quote! { .. };
// This method will work even if tokens are invalid
panic!("{}", token_stream.to_string());
Cargo Expand
cargo-expand is your friend.
cargo-expand will expand macros to code tokens, for both declarative and procedural macros:
fn main() { println!("hello {}, one {:?}, two {:.2}", "hello", 1.1, 2.5); }
Expands to:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1_formatted(
            &["hello ", ", one ", ", two ", "\n"],
            &match (&"hello", &1.1, &2.5) {
                (arg0, arg1, arg2) => [
                    ::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Display::fmt),
                    ::core::fmt::ArgumentV1::new(arg1, ::core::fmt::Debug::fmt),
                    ::core::fmt::ArgumentV1::new(arg2, ::core::fmt::Display::fmt),
                ],
            },
            &[
                ::core::fmt::rt::v1::Argument {
                    position: ::core::fmt::rt::v1::Position::At(0usize),
                    format: ::core::fmt::rt::v1::FormatSpec {
                        fill: ' ',
                        align: ::core::fmt::rt::v1::Alignment::Unknown,
                        flags: 0u32,
                        precision: ::core::fmt::rt::v1::Count::Implied,
                        width: ::core::fmt::rt::v1::Count::Implied,
                    },
                },
                ::core::fmt::rt::v1::Argument {
                    position: ::core::fmt::rt::v1::Position::At(1usize),
                    format: ::core::fmt::rt::v1::FormatSpec {
                        fill: ' ',
                        align: ::core::fmt::rt::v1::Alignment::Unknown,
                        flags: 0u32,
                        precision: ::core::fmt::rt::v1::Count::Implied,
                        width: ::core::fmt::rt::v1::Count::Implied,
                    },
                },
                ::core::fmt::rt::v1::Argument {
                    position: ::core::fmt::rt::v1::Position::At(2usize),
                    format: ::core::fmt::rt::v1::FormatSpec {
                        fill: ' ',
                        align: ::core::fmt::rt::v1::Alignment::Unknown,
                        flags: 0u32,
                        precision: ::core::fmt::rt::v1::Count::Is(2usize),
                        width: ::core::fmt::rt::v1::Count::Implied,
                    },
                },
            ],
        ));
    };
}
Why Not Macros
- 
Compilation time increases. - Code tokens have to be generated before the actual-code-to-compile exists.
- Rust has to compile proc-macro crates, then run it over your crate.
- Multiple versions of proc-macro crates means multiple compilation.
 
- 
Not IDE friendly. Generated accessors are not indexed by RLS / Rust analyzer, so you don't get "Jump to definition". 
Links
- Rust Lang Reference: Procedural Macros
- The Little Book of Rust Macros
- cargo-expand
- proc_macro_roids: Improves ergonomics when writing proc macros.