如何区分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 是一种更好的工具。它更易于阅读(并因此扩展)并免费处理新语法(如果维护 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
类型相同,并且您还需要先行确定它是类型别名还是结构,非常相似 example at syn为此。
我想编写一个基于 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 是一种更好的工具。它更易于阅读(并因此扩展)并免费处理新语法(如果维护 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
类型相同,并且您还需要先行确定它是类型别名还是结构,非常相似 example at syn为此。