12
votes

Is it possible in rust to write a macro that creates other macros. For example, suppose I define the following two macros:

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

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

Since the two macros repeat a lot of code, I may want to write a macro to generate the macros.

I've tried to generate such a meta macro.

#![feature(macro_rules)]

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

metamacro!(foo)

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

but get the following errors:

<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.

Edit: After playing around with the macros some more, I discovered that a higher order macro works as expected if the returned macro doesn't receive any arguments. For example, the following code

#![feature(macro_rules)]

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

metamacro!(foo)

fn main() {
    foo!();
}

prints hello

2
Why not just to try yourself? - Dmitry Belyaev
I have tried and failed to find something that works. I asked here in case there was a hidden or lesser known mechanism in rust that I hadn't tried yet. I'll add this to my question to make it a little clearer. - mwhittaker
You haven’t shown what you tried or what the errors are. These details are absolutely necessary to be able to give any sort of answer. - Chris Morgan

2 Answers

14
votes

This was recently made possible in #34925. The answer below refers to older versions of Rust.


This is #6795 and #6994, and isn't directly possible at the moment. E.g. the first thing one would try is

#![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() {}

But this fails with

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

(Filed #15640 about the caret pointing at the ( instead of the $e.)

The reason is macro expansion is "dumb": it naively interprets and replaces all the tokens, even inside nested macro invocations (and macro_rules! is a macro invocation). Hence, it can't tell that the $e inside the interior macro_rules! are actually meant to be left as $e: it's just trying to replace them when expanding define.

The second thing one might try is passing in extra arguments to define to place in the interior definition, so that the $e doesn't appear directly in the nested invocation:

#![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() {}

That is, I added the $interior: tt to allow capturing the $e. tt stands for token tree, which is a non-delimiter raw token (e.g. $ or some_ident) or a sequence of tokens surrounded by matching delimiters (e.g. (foo + bar fn "baz")). Unfortunately, it seems expansion is eager enough for the $e expansion to be expanded too:

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

It works fine when the nested macro has no argument itself, because there is no nested $... non-terminals and so the expansion phase doesn't ever hit the definition problem described above.


Lastly, you can get some semblance of code sharing since you can expand to macro invocations by name:

#![feature(macro_rules)]

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

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

which prints 12.

1
votes

Here is one example that works, perhaps a starting point for you:

#![feature(macro_rules)]

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

metamacro!(hello)

fn main() {
    hello!();
}