I want to write a macro_rules
based macro that will be used to wrap a series of type aliases and struct definitions. I can match on "items" with $e:item
, but this will match both aliases and structs. I would like to treat the two separately (I need to add some #[derive(...)]
just on the structs). Do I have to imitate their syntax directly by matching on something like type $name:ident = $type:ty;
or is there a better way? This route seems annoying for structs because of regular vs tuple like. If I also wanted to distinguish functions that would be really painful because they have a lot of syntactical variation.

- 20,727
- 18
- 94
- 165
1 Answers
I believe for that problem somewhat simple cases can be solved with macro_rules!
, but that probably would be limited (you can't lookahead) and super error-prone. I only cover an example for types, I hope that would be convincing enough to avoid macro_rules!
. Consider this simple macro:
macro_rules! traverse_types {
($(type $tp:ident = $alias:ident;)*) => {
$(type $tp = $alias;)*
}
}
traverse_types!(
type T = i32;
type Y = Result<i32, u64>;
);
That works fine for the trivial aliases. At some point you probably also would like to handle generic type aliases (i.e. in the form type R<Y> = ...
). Ok, your might still rewrite the macro to the recursive form (and that already a non-trivial task) to handle all of cases. Then you figure out that generics can be complex (type-bounds, lifetime parameters, where-clause, default types, etc):
type W<A: Send + Sync> = Option<A>;
type X<A: Iterator<Item = usize>> where A: 'static = Option<A>;
type Y<'a, X, Y: for<'t> Trait<'t>> = Result<&'a X, Y>;
type Z<A, B = u64> = Result<A, B>;
Probably all of these cases still can be handled with a barely readable macro_rules!
. Nevertheless it would be really hard to understand (even to the person who wrote it) what's going on. Besides, it is hard to support new syntax (e.g. impl-trait alias type T = impl K
), you may even need to have a complete rewrite of the macro. And I only cover the type aliases
part, there's more to handle for the struct
s.
My point is: one better avoid macro_rules!
for that (and similar) problem(-s), procedural macros is a way much a better tool for that. It easier to read (and thus extend) and handles new syntax for free (if syn
and quote
crates are maintained). For the type alias this can be done as simple as:
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::parse::{Parse, ParseStream};
struct TypeAliases {
aliases: Vec<syn::ItemType>,
}
impl Parse for TypeAliases {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut aliases = vec![];
while !input.is_empty() {
aliases.push(input.parse()?);
}
Ok(Self { aliases })
}
}
#[proc_macro]
pub fn traverse_types(token: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(token as TypeAliases);
// do smth with input here
// You may remove this binding by implementing a `Deref` or `quote::ToTokens` for `TypeAliases`
let aliases = input.aliases;
let gen = quote::quote! {
#(#aliases)*
};
TokenStream::from(gen)
}
For the struct
parsing code is the same using ItemStruct
type and also you need a lookahead to determine wether it's an type-alias or a struct, there's very similar example at syn for that.

- 3,166
- 14
- 28
-
Thanks this is a great answer! Between your code and the example I should be able to figure it out. – Joseph Garvin Jul 09 '20 at 14:48
-
after looking at the example, I'm not sure how I'm really saved from having to deal with all the syntactic forms -- the example still hard codes an expectation of what proper struct syntax is for example -- it doesn't support tuple structs or generics. – Joseph Garvin Jul 09 '20 at 17:54
-
You don't need to care about all of the details: `syn::ItemType::parse` do all of the parsing. All of you need is handpick the necessary details from it and do smth with it (e.g. mutate, or genereate smth else). That is fairly depends on your particular case, but still proc-macro may help you with maintaining your code. – Kitsu Jul 09 '20 at 19:45
-
ah, I was confused because the example tries to define its own definition for ItemStruct instead of using `syn::ItemStruct`. Much better! – Joseph Garvin Jul 10 '20 at 02:30
-
if I want to support `struct`, `pub struct`, `pub(crate) struct` and so on, I can't just `lookahead1` right? Do I `input.fork()`, try to parse visibility on it, then try to parse `struct` or `type` on it, then go back to using the original input, or is there a better way? Like if a new keyword that can go in front of `pub` shows up tommorow I won't support it with this solution. – Joseph Garvin Jul 10 '20 at 03:06
-
Or perhaps I fork and just try parsing a whole struct/alias. If it succeeds I won't parse again, and if it fails it should be early in the parse so maybe this is OK. – Joseph Garvin Jul 10 '20 at 03:21
-
... tried that but I can't figure out how to go ahead with using the fork if parsing succeeds. I need the state of the `input: ParseStream` to be changed to that of the fork, but I don't see how to do that other than parsing again, which is what the fork documentation specifically says to avoid. – Joseph Garvin Jul 10 '20 at 04:11
-
I believe visibility already included in both [ItemType](https://docs.rs/syn/1.0.33/syn/struct.ItemType.html) and [ItemStruct](https://docs.rs/syn/1.0.33/syn/struct.ItemStruct.html) (note **vis** field). So for lookahead you only need try to parse `ItemType`, then as a fallback an `ItemStruct`. – Kitsu Jul 10 '20 at 05:39
-
but if I failed to parse `ItemType` is the `ParseStream` still in a valid state? Does it rewind to where it was before? – Joseph Garvin Jul 10 '20 at 20:42
-
I guess you either need to clone it explicitly (since `Parse::parse` accept stream by-value), or use lookahead – Kitsu Jul 12 '20 at 09:30
-
1I ended to parsing syn::Item, then checking if the result was a struct or type alias. This way I didn't have to try recovering. Hopefully this helps somebody :) – Joseph Garvin Jul 12 '20 at 14:07