Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Higher order macros

Tags:

rust

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

like image 855
mwhittaker Avatar asked Jul 12 '14 23:07

mwhittaker


People also ask

Does macros increase code size?

During preprocessing, a macro is expanded (replaced by its definition) inline each time it's used. A function definition occurs only once regardless of how many times it's called. Macros may increase code size but don't have the overhead associated with function calls.

How many types of macros are there?

There are two types of macros: Object-like Macros. Function-like Macros.


2 Answers

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.

like image 90
huon Avatar answered Jan 02 '23 21:01

huon


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!();
}
like image 44
errordeveloper Avatar answered Jan 02 '23 22:01

errordeveloper