如何编写宏实现两个枚举类型的类型论并集?
Rust多枚举合并宏的实现方案
问题核心
要实现#[unionize(ErrorA, ErrorB)]这类宏,核心难点是自动生成原枚举到合并后枚举的Into实现,同时支持嵌套/扁平两种合并形式,且能处理跨模块、跨crate的枚举。
嵌套式合并的最简实现
嵌套式合并的Into实现无需解析原枚举AST,直接基于枚举变体构造即可:
宏生成的枚举定义
enum Error { ErrorA(ErrorA), ErrorB(ErrorB), }
自动生成的From实现(自动获得Into能力)
impl From<ErrorA> for Error { fn from(err: ErrorA) -> Self { Error::ErrorA(err) } } impl From<ErrorB> for Error { fn from(err: ErrorB) -> Self { Error::ErrorB(err) } }
这种实现仅需知道枚举的类型路径,过程宏可以轻松生成这段代码,完全不需要访问原枚举的内部结构。
扁平式合并的实现(无变体冲突时)
扁平合并需要获取原枚举的变体列表,这得借助syn和quote库编写过程宏:
核心步骤
- 解析宏参数中的枚举类型路径
- 使用
syn解析每个原枚举的AST,提取变体定义 - 检测变体名是否冲突,无冲突则生成扁平枚举
- 为每个原枚举的变体生成转换逻辑,进而实现
From
过程宏示例代码
use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemEnum, Path, punctuated::Punctuated, Token}; #[proc_macro_attribute] pub fn unionize(args: TokenStream, input: TokenStream) -> TokenStream { // 解析宏参数中的枚举列表 let enums: Punctuated<Path, Token![,]> = parse_macro_input!(args); // 解析目标枚举的骨架 let mut merged_enum = parse_macro_input!(input as ItemEnum); let merged_ident = &merged_enum.ident; let mut variants = Vec::new(); let mut from_impls = Vec::new(); for enum_path in enums { // 解析原枚举的完整定义 let enum_def = syn::parse_str::<ItemEnum>(&format!("{}", quote!(#enum_path))).unwrap(); let enum_variants = &enum_def.variants; // 收集原枚举的变体到合并枚举中 variants.extend(enum_variants.clone()); // 生成原枚举到合并枚举的From实现 let match_arms = enum_variants.iter().map(|variant| { let variant_ident = &variant.ident; let fields = match &variant.fields { syn::Fields::Named(_) => quote!({..}), syn::Fields::Unnamed(_) => quote!((..)), syn::Fields::Unit => quote!(), }; quote! { #enum_path::#variant_ident #fields => #merged_ident::#variant_ident #fields, } }); from_impls.push(quote! { impl From<#enum_path> for #merged_ident { fn from(val: #enum_path) -> Self { match val { #(#match_arms)* } } } }); } // 替换合并枚举的变体列表 merged_enum.variants = Punctuated::from_iter(variants); // 输出最终代码 let expanded = quote! { #merged_enum #(#from_impls)* }; expanded.into() }
关键细节处理
- 变体冲突:在收集变体时,需检查是否有重复的变体名,若存在则自动切换为嵌套模式,或抛出编译错误提示用户
- 跨crate访问:确保原枚举在使用宏的作用域中已被导入(如
use other_crate::ErrorA;),syn才能正确解析其AST - 灵活配置:可扩展宏参数,让用户指定合并模式(如
#[unionize(flat(ErrorA, ErrorB))]或#[unionize(nested(ErrorA, ErrorB))])
内容的提问来源于stack exchange,提问作者Ákos Vandra-Meyer
相关产品推荐
相关产品推荐

