Contents

Rust Proc Macros

Fun with Proc Macros

This will be a super short post about some of my thoughts messing around and using proc macros in Rust

What is a Macro

To understand proc macros, it’s important to understand what a macro is.

Macros are in a way plugins for the compiler. They take tokens and return Rust syntax to replace the tokens. Often times macros are used to avoid boilerplate code and repeating the same code over and over again.

Lets demonstrate with an example!

Lets say you want to implement a trait MyTrait on three types A, B and C, but all three implementations will be the same.

The following could be MyTrait:

1
2
3
4
5
6
7
trait MyTrait {
    fn get_u32(&self) -> u32;
}

struct A {}
struct B {}
struct C {}

Without macros, you would have to do:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
impl MyTrait for A {
    fn get_u32(&self) -> u32 {
       ...
    }
}

impl MyTrait for B {
    fn get_u32(&self) -> u32 {
       ...
    }
}

impl MyTrait for C {
    fn get_u32(&self) -> u32 {
       ...
    }
}

With macros you can define the macro (I’m calling it impl_my_trait):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
macro_rules! impl_my_trait {
    ($(t:ty),*) => {
        $(
            impl MyTrait for $t {
                fn get_u32(&self) -> u32 {
                    ...
                }
            }
        )*
    }
}

Then call it like the following:

1
impl_my_trait!(A, B, C);

The above would expand into the exact same repeated code we saw earlier!

Note
You can probably use Generics and a blanket implementation in this case… but that’s not the point of this post 😆

I’m glossing over the syntax and how they work, more details on macro syntax in the amazing chapter in the rust book

Proc macros!

Proc macros, short for procedural macros are a form of macros, that are written as Rust programs!

Wait, so why would I use proc macros?

Because normal macros are limited in syntax and you would have to resort to writing recursive macros for anything complicated. Proc macros are rust programs, meaning you can take advantage of the whole rust language!

But… how does that work, I thought macros run before compilation? how do you get a Rust program to run… before the Rust program is compiled 😖

Well, that’s the root of why all proc-macros have to live on their own crate, with proc-macro = true in the Cargo.toml!

Writing proc macros

Writing proc macros always starts and ends with TokenStreams. As the name suggests, those are a stream of Rust Tokens (they don’t need to be a valid Rust program though!)

A proc macro takes a TokenStream and returns one. The proc macro’s job would be to transform the input token stream into an output one, using Rust.

There are some AMAZING tools that help a lot with that process, almost all are written by dtolnay 🔥 The most useful ones for me so far have been syn, quote and proc_macro2

There are multiple types of proc macros, and syntax to invoke them, so I would suggest readers to check out the reference in the Rust book

An example proc macro definition looks like the following, this is taken from an open-source workshop on proc-macros, written by dtolnay (totally mind boggling how one person can be so impactful to a community, if you ever read this, you are incredible!)

1
2
3
4
5
6
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    .
    .
    .
}

You can view the workshop on it’s github repository

You can also find my implementation of the first exercise on my fork

Conclusion

That’s all! This was short, and didn’t go into details at all… but hopefully the resources I linked throughout can provide the reader with more information. I’m planning to start writing short posts similar to this in the future with random thoughts 🕺