5

I have a three different functions, of which I want to call one based on a macro argument. This argument should be pre-processed, which is why I thought I need to write it as expr. However, I can't seem to find a way to distinguish different cases for an expr in a macro. Here is my code:

fn func_100(){
    println!("Func 100!");
}
fn func_200(){
    println!("Func 200!");
}
fn func_300(){
    println!("Func 300!");
}

macro_rules! generate_func_call {
    (100) => {
        func_100();
    };
    (200) => {
        func_200();
    };
    (300) => {
        func_300();
    }
}

macro_rules! generate_func_call_wrapper {
    ($func: ident, $number: expr) => {
        fn $func(){
            println!("{:?}", $number / 100);
            generate_func_call!($number);
        }
    };
}

generate_func_call_wrapper!(f1,100);
generate_func_call_wrapper!(f2,200);
generate_func_call_wrapper!(f3,300);

fn main(){
    f1();
}

which generates the following compile time error:

    generate_func_call!($number);
                        ^^^^^^^ no rules expected this token in macro call

How can I fix this program so that calls a different function based on the $number expression?

MLStudent
  • 51
  • 1
  • 2
  • 1
    TBH the Rust compiler is pretty good in optimizing, so if you are using constants in an `if` expression it will eliminate that for you, so it has no impact on runtime. – hellow Apr 23 '19 at 05:41
  • 1
    Very related (if not dup): https://stackoverflow.com/q/39349286 – hellow Apr 23 '19 at 05:47
  • 3
    Possible duplicate of [Can macros match against constant arguments instead of literals?](https://stackoverflow.com/questions/39349286/can-macros-match-against-constant-arguments-instead-of-literals) – Jmb Apr 23 '19 at 08:16

2 Answers2

6

You can see the macro expansion by calling cargo +nightly rustc --profile=check -- -Zunstable-options --pretty=expanded or using cargo-expand

fn f1() {
    {
        ::std::io::_print(::std::fmt::Arguments::new_v1(
            &["", "\n"],
            &match (&(100 / 100),) {
                (arg0,) => [::std::fmt::ArgumentV1::new(arg0, ::std::fmt::Debug::fmt)],
            },
        ));
    };
    ();
}

You can see the last (); which should have been func_100()

This is beacuse there is no token rule in generate_func_call of type ($number: expr) i.e. there is no rule which matches the expansion. This is beacuse $number is not replaced by 100 as you would expect in a function. The macro simply creates more rust code based on the fragment types it receives, it does not try to evaluate anything.

Change the code to:

macro_rules! generate_func_call {
    ($number: expr) => {
        match $number {
            100 => func_100(),
            200 => func_200(),
            300 => func_300(),
            _ => (),
        }
    };
}

And finally (); changes to:

match 300 {
    100 => func_100(),
    200 => func_200(),
    300 => func_300(),
    _ => (),
};

You don't have to worry about an extra jump statement or such, it's gets optimized as 300 is compile time constant. and it just changes into func_300().

sn99
  • 843
  • 8
  • 24
0

Looks like it's simply not supported.

Simmilar Issue #1 Simmilar Issue #2

A possible workaround is to match against the literal in the first macro, like in this playground. Although that just introduces more code repetition.

In the future it might be possible to do this using the const fn feature like in this playground. But you'll have to wait for the feature to be implemented.

Right now I'd advise to simply match against the value at runtime using a regular match.

Dzejkop
  • 93
  • 8