I’ve made some good progress on the procedural macros workshop today. I think for the first time since I started this little exercise, I am actually feeling reasonably confident about my understanding of macros.
I’ve been able to abstract some simple tasks such as defining an Ident struct for the builder, or ensuring that we can extract named fields from the struct into two declarative macros. It would be nice if there were a simple way to check the types of the inputs to the macro (I am aware of ty. That’s not what I’m looking for). But I suppose because the macro’s code will be substituted directly into the AST, if there is a syntax error brought on by incorrect typing, the compiler will still pick it up.
In both cases I am assuming that the type of the input is DeriveInput. As I say, I’m not sure if there is any better way to enforce this.
I’ve gone back and rewritten a lot of my initial builder solution, this time breaking everything up into smaller functions. My lack of understanding earlier in the week meant that I was trying to cram everything into a single call to quote!, but after taking a step back I have been able to break everything up into smaller, distinct chunks which I think leads to much more readable, easier to maintain code.
A few nice, simple patterns have emerged as well. For example, there is some conditional logic involved in assigning the builder fields to the concrete instance’s fields depending on whether or not a field is optional. There doesn’t seem to be any kind of conditional logic in quote!, so this needs to be handled in Rust. I found that the following little pattern worked quite well—using an iterator to generate individual TokenStreams for each field, depending on how it should be handled, then pushing those TokenStreams into a single parent stream which is returned.
This function can then be called from elsewhere in the macro, and the resulting tokens can be included in a new TokenStream
A combination of little patterns like this and better modularization of the code have (of course) made it much easier to extend my code to meet David’s expanding requirements in the workshop. The full solution currently looks as follows. With some improvements to naming conventions, this feels like it is approaching a much better solution than the one I posted previously:
That’s all for today folks, but it feels like we’re gradually approaching a (moderate) understanding of how Rust macros work.