Procedural macros are a really powerful language feature in Rust and something I haven’t seen in many other languages.
There are a heap of tutorials out there for procedural macros, including in The Rust Reference, and the first edition of the Rust Book. One of the more entertaining (and useful) posts is by Zach Mitchell where you get to “learn Rust procedural macros with Nic Cage”.
I won’t go into depth about what procedural macros are and why they’re so powerful. But basically they allow you to tell the compiler to take in some code, analyse it, and generate some more code. To me, that sounds pretty powerful already.
I recently put up a crate called
VariantEq which exposes a Custom Derive type procedural macro called
Custom Derive macros are used with
#[derive(Debug, ...)] above your struct or enum. And the job of this is usually to implement a trait for you! In this case it’s the
Examples are better than explanations so this is what deriving
VariantEq on your enum will allow you to do.
Pretty much it implements the
Eq traits in a way that only the variant is considered, and the variant fields are ignored.
With all the tutorials and examples around on the web I thought I would have no trouble implementing this. But turns out, there were some recent changes to the most up to date way of implementing these macros. The docs on the web hadn’t been fully updated (or maybe I just couldn’t find up to date examples) so it became a bit harder than I thought.
In any case this can be yet another example of using procedural macros in Rust.
Exposing the Macro
A good place to start is by exposing the macro you are creating. This will tell the compiler what to run when you use
To find a good example of how to do with with
proc_macro2, I ended up looking through the
diesel_derives source which sets out the code to do this pretty nicely.
First we define the entry point
varianteq_derive. This function actually has a procedural macro on itself, which marks the function to be called whenever we
expand_derive functions is fairly straight forward. It takes the
proc_macro, converts it into a
proc_macro2::TokenStream, parses it, and then calls our derive function, in this case
Proc Macro 2
proc_macro2is a small shim over the
proc_macrocrate in the compiler intended to multiplex the current stable interface and the upcoming richer interface.
Deriving the Macro
src/varianteq.rs we have the
derive function that was being called earlier. The logic can be broken up into three stages.
First, we gather information from the
DeriveInput, which was parsed out of the
TokenStream earlier on. For
VariantEq specifically we just need the enum identifier, and the variants of the enum.
Next, we construct the list of variants. This is essentially a mapping of each enum variant into our
EnumVariant type which can be used to generate tokens. More on that soon.
Finally, we generate our tokens with the
quote! macro. This macro takes Rust code, and parses it into the
Tokens that we need to give back to the compiler. This is to avoid manually specifying each individual token of code to generate.
This last part is fairly straight forward. But it has one line which is a bit mystifying. The
#(#enum_variants => true,)* is a special syntax used by the
quote! macro to bring values from outside its scope into scope.
For a specific explanation of what this line, and similar syntax, does: from the the docs for quote/quote on interpolation:
This iterates through the elements of any variable interpolated within the repetition and inserts a copy of the repetition body for each one.
Our Own EnumVariant
Now back to the
EnumVariant type I mentioned earlier, set out in
src/token.rs. This struct is just an abstraction to make it easier to use that special interpolation syntax in the
The important bit is that
EnumVariant implements the
ToTokens trait which defines how it gets generated into tokens.
Let’s say we have this enum:
ToTokens implementation for
EnumVariant will spit out the tokens for this Rust code, generating a different line for each variant based on the variant type:
Running back up the function calls, this output from
EnumVariant::to_tokens is plugged back into the
quote! block defined in
Now we have
Eq implemented for the enum that derived
VariantEq. So we turn that Rust code into Tokens and send it back to the compiler.