如何区分macro_rules宏中不同种类的项目?

How to distinguish different kinds of items in macro_rules macros?

我想编写一个基于 macro_rules 的宏,用于包装一系列类型别名和结构定义。我可以用 $e:item 匹配“项目”,但这将同时匹配别名和结构。我想分别对待这两者(我需要在结构上添加一些 #[derive(...)] )。我是否必须通过匹配 type $name:ident = $type:ty; 之类的东西来直接模仿他们的语法,或者有更好的方法吗?由于常规 vs 元组之类,这条路线对于结构来说似乎很烦人。如果我也想区分函数,那将是非常痛苦的,因为它们有很多语法变化。

我相信对于这个问题,一些简单的情况可以用 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>;
);

这对于普通的别名来说效果很好。在某些时候,您可能还想处理泛型类型别名(即 type R<Y> = ... 形式)。好的,您可能仍将宏重写为递归形式(这已经是一项重要的任务)来处理所有情况。然后你发现泛型可能很复杂(类型边界、生命周期参数、where 子句、默认类型等):

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!procedural macros 是一种更好的工具。它更易于阅读(并因此扩展)并免费处理新语法(如果维护 synquote 板条箱)。对于类型别名,这可以简单地完成:

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 类型相同,并且您还需要先行确定它是类型别名还是结构,非常相似 example at syn为此。