高阶宏

13

在 Rust 中是否可能编写一个创建其他宏的宏。例如,假设我定义了以下两个宏:

macro_rules! myprint(
    ($a:expr) => (print!("{}", $a))
)

macro_rules! myprintln(
    ($a:expr) => (println!("{}", $a))
)

由于这两个宏重复了很多代码,我可能想编写一个宏来生成这些宏。

我尝试生成这样的元宏

#![feature(macro_rules)]

macro_rules! metamacro(
    ($i:ident) => (
        macro_rules! $i (
            ($a:expr) => ({println!("hello {}", $a)})
        )
    );
)

metamacro!(foo)

fn main() {
    foo!(1i);
}

但是遇到了以下错误:

<anon>:6:13: 6:14 error: unknown macro variable `a`
<anon>:6             ($a:expr) => ({println!("hello {}", $a)})
                     ^
playpen: application terminated with error code 101
Program ended.

编辑:在进一步尝试宏后,我发现如果返回的宏没有接收到任何参数,则一个高阶宏会按预期工作。例如,以下代码

#![feature(macro_rules)]

macro_rules! metamacro(
    ($i:ident) => (
        macro_rules! $i (
            () => ({println!("hello")})
        )
    );
)

metamacro!(foo)

fn main() {
    foo!();
}

打印 hello


1
为什么不自己尝试一下呢? - Dmitry Belyaev
4
我曾试过但未能找到有效的解决方案。我在这里提问,以防有一些隐藏或不太为人知的 Rust 机制是我尚未尝试过的。我会将这个问题补充说明,以使其更加清晰明了。 - mwhittaker
你还没有展示你尝试过什么,或者错误是什么。这些细节对于能够给出任何答案来说绝对是必要的。 - Chris Morgan
3个回答

14

最近在#34925中实现了这一功能。下面的回答是关于较旧版本的Rust。


目前还不直接支持#6795#6994。例如,第一件尝试的事情是:

#![feature(macro_rules)]

macro_rules! define {
    ($name: ident, $other: ident) => {
        macro_rules! $name {
            ($e: expr) => {
                $other!("{}", $e)
            }
        }
    }
}

define!{myprintln, println}
define!{myprint, print}

fn main() {}

但这种方法失败了

so8.rs:6:13: 6:14 error: unknown macro variable `e`
so8.rs:6             ($e: expr) => {
                     ^

(关于插入符号指向 ( 而不是 $e 的问题,见#15640。)

原因是宏展开是“愚蠢的”:它简单地解释和替换所有标记,甚至在嵌套宏调用中也是如此(而macro_rules!就是一个宏调用)。因此,它不能确定内部 macro_rules! 中的 $e 实际上应该保留为 $e:在扩展 define 时只是尝试替换它们。

第二件事是尝试传递额外的参数到 define 中,以便将其放置在内部定义中,这样 $e 就不会直接出现在嵌套调用中:

#![feature(macro_rules)]

macro_rules! define {
    ($name: ident, $other: ident, $($interior: tt)*) => {
        macro_rules! $name {
            ($($interior)*: expr) => {
                $other!("{}", $($interior)*)
            }
        }
    }
}

define!{myprintln, println, $e}
define!{myprint, print, $e}

fn main() {}

也就是说,我添加了$interior: tt来捕获$e。这里的tt代表标记树,它是一个非分隔符原始标记(例如$some_ident),或者是由匹配分隔符包围的一系列标记(例如(foo + bar fn "baz"))。不幸的是,扩展似乎很渴望将$e扩展也扩展出来:

so8.rs:8:13: 8:14 error: unknown macro variable `e`
so8.rs:8             ($($interior)*: expr) => {
                     ^

如果嵌套的宏没有自己的参数,那么它可以很好地工作,因为没有嵌套的$...非终端符号,所以扩展阶段永远不会遇到上述描述的定义问题。


最后,您可以通过名称扩展到宏调用,从而获得一些代码共享的相似性:

#![feature(macro_rules)]

macro_rules! my {
    ($name: ident, $e: expr) => {
        $name!("{}", $e)
    }
}

fn main() {
    my!(print, 1i);
    my!(println, 2i);
}

打印出12的命令。


我认为,也许我们要么使其正常工作,要么完全禁止嵌套宏... - errordeveloper
哇,太棒了!你知道编程语言中是否计划在未来加入更高阶的宏吗? - mwhittaker
@mwhittaker,目前没有针对此计划,但肯定是一个很烦人的问题,需要解决。 - huon
@mwhittaker(还有一些覆盖它的错误;我在我的答案开头添加了链接。) - huon

1

这里有一个工作的例子,也许可以为您提供一个起点:

#![feature(macro_rules)]

macro_rules! metamacro(
    ($i:ident) => (
        macro_rules! $i (
            () => ({println!("hello!")})
        )
    );
)

metamacro!(hello)

fn main() {
    hello!();
}

错误:宏展开为项必须用大括号括起来或跟着分号。 - CS QGB

1

有一种不太好看的方法:

macro_rules! metamacro {
    ($sign: tt $i: ident) => {
        macro_rules! $i {
            ($sign a: expr) => { println!("hello {}", $sign a) };
        }
    };
}

metamacro!{ $foo }

fn main() {
    foo!("1i");
}

我不太清楚这是如何工作的,但这段代码片段可以在2018年编译和运行。 尽管当宏的括号内有很多语法时,会变得有点丑陋。 这只是解决实际问题的一种方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接