12

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.

2 Answers 2

13

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.

Sign up to request clarification or add additional context in comments.

Comments

0

While explicitly passing identifiers where possible should be preferred, a nested macro_rules! can be used to provide access to a local, bypassing hygiene:

macro_rules! generate_func { ($name:ident) => { fn $name(param: i32) { macro_rules! param { () => { param } } generate_body!() } }; } macro_rules! generate_body { () => {{ println!("{}", &param!()); }} } generate_func!(foo); 

This isn't that beneficial in the above example, but can be quite useful if the body is a macro parameter:

macro_rules! generate_func { ($name:ident, $body:expr) => { fn $name(param: i32) { macro_rules! param { () => { param } } $body } }; } generate_func!(foo, { println!("{}", &param!()); }); 

Here to do proper hygiene you'd need to make the body a lambda (thereby making it impossible to transfer control around the lambda boundary in more complex cases, and mandating writing that out even if a specific parameter isn't needed), but nested macro_rules! allows bypassing that problem.

Another option is to make the macro's param a hygienic name from the invocation place:

macro_rules! generate_func { ($name:ident, $param:ident, $body:expr) => { fn $name($param: i32) { $body } }; } generate_func!(foo, param, { println!("{}", &param); }); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.