Easy `proc_macro_derive`s with `synstructure`

25 August 2018

Recently, I found myself in the market for some quickcheck. However, there were custom types, which had no Arbitrary implementation. Wondering if someone had already written a procedural macro to derive it, I found panicbit’s quickcheck_derive crate. However, to my dismay, it was severely limited in that it could only derive Arbitrary for structs.

“How hard could it be?” thought I, forked the repo and looked at the code, which appears to be written roughly a year ago. The used syn was also quite old, so I looked around to find if there were some improvements I could use.

I was in luck – what I found was the synstructure crate which makes writing procedural macros a breeze (as a testament, the git stats show that I deleted more lines than I added, despite adding a sizable chunk of new functionality). Also the crate is well-documented, so you can find examples for most things you may like to do if you search around for a bit.

synstructure gives us a few macros to work with structs and enums. First, we import the necessary stuff:

extern crate syn ; #[macro_use] extern crate synstructure ; extern crate proc_macro2 ; use proc_macro2 :: TokenStream ;

This allows us to write our procedural macro, which has the form fn my_derive(s: synstructure::Structure) -> TokenStream and can be registered with decl_derive!([MyDerive] => my_derive); .

synstructure::Structure has many useful methods to construct or match over values. The variants() method returns a slice of VariantInfo for all variants (one for structs, any number for enums), which one can iterate to generate code for each variants. There is also an .each(..) method to generate matches.

To allow for easy testing, there’s a test_derive! macro which you feed a struct or enum plus the expected generated code and it will expand and compile both and check if they are equal. This looks like the following:

#[test] fn test_my_derived_struct () { test_derive! { my_derive { struct MyStruct ( u32 ); } expands to { .. } } }

Pro-tip: Don’t write the expected result by hand. Instead write fn eek() {} and copy the expected result from the error message. See? I didn’t even have to write many lines of code!