You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用声明式宏生成自包含枚举?解决逗号歧义问题

递归枚举声明式宏的歧义问题解决

问题背景

需要创建声明式宏简化递归枚举PFormula的实例创建,自动隐藏Box::new的调用,但处理包含逗号的深层子结构时出现编译歧义。

枚举定义

#[derive(Debug)]
enum PFormula {
    True,
    False,
    Atom(i32),
    Not(Box<PFormula>),
    And(Box<PFormula>, Box<PFormula>)
}

原宏定义

macro_rules! pformula {
    ($func:tt($($($sub:tt)+),+)) => { PFormula::$func($(Box::new($sub)+),+) };
    (True) => { PFormula::True };
    (False) => { PFormula::False };
    ($e:expr) => { PFormula::Atom($e) };
}

测试代码

fn main() {
    let x = pformula!(And(Not(2), 3));
    let y = pformula!(And(And(1, 4), And(5, And(2, 6))));
    println!("{:?}", x);
    println!("{:?}", y);
}

期望展开结果

以x为例,期望展开为:

PFormula::And(Box::new(PFormula::Not(Box::new(PFormula::Atom(2)))), Box::new(PFormula::Atom(3)))

编译错误

error: local ambiguity when calling macro `pformula`: multiple parsing options: built-in NTs tt ('sub') or 1 other option.
  --> src/main.rs:18:33
   |
18 |     let x = pformula!(And(Not(2), 3));
   |                                 ^

error: local ambiguity when calling macro `pformula`: multiple parsing options: built-in NTs tt ('sub') or 1 other option.
  --> src/main.rs:19:36
   |
19 |     let y = pformula!(And(And(1, 4), And(5, And(2, 6))));
   |                                    ^

error: could not compile `playground` due to 2 previous errors

解决方案:声明式宏即可解决

原宏的问题在于使用$($sub:tt)+匹配子结构时,tt(token tree)的贪婪匹配特性导致宏无法区分逗号是子结构内部的分隔符还是变体参数的分隔符,从而产生歧义。我们可以通过按变体的参数元数分别匹配来消除歧义:

优化后的宏定义

macro_rules! pformula {
    // 处理一元参数变体(如Not)
    ($variant:tt($arg:tt)) => {
        PFormula::$variant(Box::new(pformula!($arg)))
    };
    // 处理二元参数变体(如And)
    ($variant:tt($arg1:tt, $arg2:tt)) => {
        PFormula::$variant(
            Box::new(pformula!($arg1)),
            Box::new(pformula!($arg2))
        )
    };
    // 基础原子/常量情况
    (True) => { PFormula::True };
    (False) => { PFormula::False };
    ($val:expr) => { PFormula::Atom($val) };
}

原理说明

  1. 按元数分组匹配:针对一元、二元变体分别编写分支,明确参数的数量和分隔方式,彻底避免逗号歧义。
  2. 递归调用宏:每个子结构都会递归调用pformula!,自动处理嵌套的变体,无需手动写Box::new。
  3. 兼容所有同元数变体:不管你有8个一元变体还是5个二元变体,只要调用时符合变体(子结构)或变体(子结构1, 子结构2)的格式,宏都会正确处理。

测试验证

优化后的宏可以正确编译原测试代码,x和y会按期望展开,运行后能正常打印出对应的枚举结构。

关于过程宏的选择

如果后续枚举变体的元数更多(如三元、四元)、结构更复杂,或者需要支持更灵活的语法(如运算符重载式的写法),过程宏会更适合。但在当前场景下,声明式宏足够轻量、简洁,完全能解决问题,不需要引入额外的proc-macro依赖。

内容的提问来源于stack exchange,提问作者Noice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:41:23