Macros 如何在不重复规则的情况下为宏编写包装器?

Macros 如何在不重复规则的情况下为宏编写包装器?,macros,rust,Macros,Rust,我正在尝试为宏制作一个包装器。问题是我不想在两个宏中重复相同的规则。有办法吗 以下是我尝试过的: macro_rules! inner { ($test:ident) => { stringify!($test) }; ($test:ident.run()) => { format!("{}.run()", stringify!($test)) }; } macro_rules! outer { ($expression:expr) => {

我正在尝试为宏制作一个包装器。问题是我不想在两个宏中重复相同的规则。有办法吗

以下是我尝试过的:

macro_rules! inner {
    ($test:ident) => { stringify!($test) };
    ($test:ident.run()) => { format!("{}.run()", stringify!($test)) };
}

macro_rules! outer {
    ($expression:expr) => {
        println!("{}", inner!($expression));
    }
}

fn main() {
    println!("{}", inner!(test));
    println!("{}", inner!(test.run()));
    outer!(test);
    outer!(test.run());
}
但我得到了以下错误:

src/main.rs:8:31: 8:42 error: expected ident, found test
src/main.rs:8         println!("{}", inner!($expression));
                                            ^~~~~~~~~~~
如果为此更改
outer
宏,代码将编译:

macro_rules! outer {
    ($expression:expr) => {
        println!("{}", stringify!($expression));
    }
}

我做错了什么?

宏规则比您可能意识到的更聪明、更愚蠢

最初,宏的所有输入都作为未区分的标记汤开始使用。这里有一个
Ident
,那里有一个
StrLit
,等等。但是,当您匹配并捕获一点输入时,通常会在抽象语法树节点中解析输入;
expr
就是这种情况

“聪明”的一点是,当您替换此捕获(例如,
$expression
)时,您不仅仅替换最初匹配的令牌:您将整个AST节点替换为单个令牌。所以现在输出中有一个奇怪的非真正的令牌,它是一个完整的语法元素

“愚蠢”的一点是,这个过程基本上是不可逆转的,而且大部分是完全看不见的。让我们以你为例:

outer!(test);
我们通过一个级别的扩展来实现这一点,它变成了:

println!("{}", inner!(test));
除了,这不是它看起来的样子。为了让事情更清楚,我将发明一些非标准语法:

假设
$(test):expr
是单个标记:它是一个表达式,可以由标记序列
test
表示。它不仅仅是令牌序列
test
。这很重要,因为当宏解释器开始展开
内部时宏,它检查第一条规则:

    ($test:ident) => { stringify!($test) };
问题在于
$(test):expr
是一个表达式,而不是标识符。是的,它包含一个标识符,但是宏解释器看起来没有那么深。它看到一个表情就放弃了

出于同样的原因,它与第二条规则不匹配

那你是做什么的。。。那要看情况了。如果
outer
不会对其输入进行任何类型的处理,您可以使用
tt
匹配器:

macro_rules! outer {
    ($($tts:tt)*) => {
        println!("{}", inner!($($tts)*));
    }
}
tt
将匹配任何令牌树(请参阅)<代码>$($tts:tt)*
将匹配任何令牌序列,而不会更改它们。这是一种将一组令牌安全地转发到另一个宏的方法

如果您需要对输入进行处理并将其转发到
内部宏。。。你可能不得不重复这些规则

macro_rules! outer {
    ($($tts:tt)*) => {
        println!("{}", inner!($($tts)*));
    }
}