3
votes

I would like to use macros to generate the body of a function, but to do so they need to access variables in the local scope:

macro_rules! generate_func {
    ($name:ident) => {
        fn $name(param: i32) {
            generate_body!()
        }
    };
}

macro_rules! generate_body {
    () => {
        println!("{}", &param);
    }
}

generate_func!(foo);
error[E0425]: cannot find value `param` in this scope
  --> src/lib.rs:11:25
   |
11 |         println!("{}", &param);
   |                         ^^^^^ not found in this scope
...
15 | generate_func!(foo);
   | -------------------- in this macro invocation
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

Playground link

It's very frustrating that this doesn't work, because I can run cargo-expand and see that the resulting code is valid:

fn foo(param: i32) {

    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(&["", "\n"],
                                                         &match (&&param,) {
                                                              (arg0,) =>
                                                              [::core::fmt::ArgumentV1::new(arg0,
                                                                                            ::core::fmt::Display::fmt)],
                                                          }));
    }
}

(this is a bit messy because of the println!, but you can see the valid reference there)

I can even copy and paste the expansion into my source and the compiler accepts it. Surely there is some way to accomplish the desired outcome?

It seems roughly related to:

But I couldn't be sure that I had the exact same issue; my case seems more basic.

1

1 Answers

5
votes

Rust macros are hygienic, which prevents identifiers from one scope leaking into another and causing all manner of mayhem. But you can solve this particular problem by passing param from generate_func to generate_body:

macro_rules! generate_func {
    ($name:ident) => {
        fn $name(param: i32) {
            generate_body!(param)
        }
    };
}

macro_rules! generate_body {
    ($param:ident) => {
        println!("{}", &$param);
    }
}

generate_func!(foo);

See it on the Playground.