Я считаю, что для этой проблемы несколько простых случаев можно решить с помощью macro_rules!
, но это, вероятно, будет ограничено (вы не можете смотреть вперед) и очень подвержено ошибкам. Я привожу пример только для типов, надеюсь, это будет достаточно убедительно, чтобы избежать macro_rules!
. Рассмотрим этот простой макрос:
macro_rules! traverse_types {
($(type $tp:ident = $alias:ident;)*) => {
$(type $tp = $alias;)*
}
}
traverse_types!(
type T = i32;
type Y = Result<i32, u64>;
);
Он отлично подходит для тривиальных псевдонимов. В какой-то момент вы, вероятно, также захотите обрабатывать общие псевдонимы типа c (т.е. в форме type R<Y> = ...
). Хорошо, вы все равно можете переписать макрос в рекурсивной форме (а это уже нетривиальная задача) для обработки всех случаев. Затем вы понимаете, что универсальные шаблоны могут быть сложными (границы типов, параметры времени жизни, предложение where, типы по умолчанию и т. Д. c):
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>;
Вероятно, все эти случаи все еще можно обрабатывать с помощью еле читаемый macro_rules!
. Тем не менее, было бы действительно трудно понять (даже человеку, который это написал), что происходит. Кроме того, сложно поддерживать новый синтаксис (например, impl-trait alias type T = impl K
), вам даже может потребоваться полностью переписать макрос. И я покрываю только часть type aliases
, есть еще кое-что, что нужно сделать для struct
s.
Моя точка зрения: лучше избегать macro_rules!
для этой (и аналогичной) проблемы (-ов), процедурный макрос - намного лучший инструмент для этого. Его легче читать (и, следовательно, расширять), и новый синтаксис обрабатывается бесплатно (если поддерживаются syn
и quote
ящики). Для псевдонима типа это можно сделать так же просто, как:
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)
}
Для кода синтаксического анализа struct
то же самое, что и для типа ItemStruct
, а также вам потребуется предварительный просмотр, чтобы определить, является ли это псевдонимом типа или структуру, для этого есть очень похожий пример в syn .