In Rust, you might have written macros to define structs, functions etc. that are similar but not the same. This is a technique that can make these macros nicer in some cases. I’m sure it has been used before, but I haven’t seen it described anywhere yet.
For instance, say you have two structs, one dealing with u8, one with u16:
This is way too much repetition for my tastes. It would be easy to forget one important trait or method implementation for one struct, or for them to go out of sync in some way.
The only differences between the structs are:
the type of the data field
the FLUIDITY constant has a different value
as a consequence of the different data types, the bodies of span() are not the same
and the same for pulsewidth()
Let’s put aside the possibility of reducing repetition and enforcing uniformity with a trait. Traits may be a good fit for this toy example, but they aren’t always what you want.
So, how does this look when we use macros? I might write something like this:
For the span() function, I was able to confine the difference to just one function call through clever (or horrible?) use of .into(), so all that needs to be passed in is the function name.
For the pulsewidth() function, I abstracted the parts of the methods that are different into a closure which needs to be passed in. It works, but the macro invocation starts to get unwieldy already.
Note that every difference becomes a macro argument. When you have many small differences, the macro argument list gets longer and longer.
Create two macros, choose_xtal! and choose_tha! like this:
and pass them into the create_struct! macro like so:
When we’re generating Xtal, $choose! will always evaluate to the expression after Xtal => and vice versa.
That means you’re now able to express the differences inline:
I think this is wonderful for readability. Instead of looking up the position of the $string_func in the argument list and then looking up the value used in the invocation of create_struct!, String::from_utf8 and String::from_utf16 are specified right where they are used. And there is no more shoehorning with .into().
The full example
The above definitions of choose_xtal! and choose_tha! only work for expression fragments. To make it work for types and literals, I added match arms with a tag in front to make sure we don’t accidentally match the wrong arm:
Same for choose_tha! with $b, and of course more fragment specifiers can be added as needed. And here is the full example.
So I should use this argument passing style everywhere, right?
Of course not. When the argument is used multiple times, you’d need to repeat yourself when inlining it into the macro body. So in these cases, you might not win anything with this technique and instead introduce new opportunities for errors.
Even when an argument is just used once, I wouldn’t blindly do this – e.g. maybe using $choose for the data type is already too much here, maybe it’s clearer to see the data type mentioned in the macro invocation.
I think the biggest benefit comes from using $choose for expressions, like the ones in span() and pulsewidth().
This technique can probably be extended and prettified, e.g. to allow something like $choose!(Xtal => foo, _ => bar). But the basic version has already been useful for me.