Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating environment for closure in a macro in Rust

I'm trying to achieve something like this (simplified):

macro_rules! atest {
    ($closure:tt) => {
        let x = 5;
        println!("Result is {}", $closure())
    };
}

fn main() {
    //let x = 50;
    atest!((|| 5 + x));
}

It does not work because the argument to the atest macro is considered by the compiler before macro evaluation:

error[E0425]: cannot find value `x` in this scope
  --> src/main.rs:10:20
   |
10 |     atest!((|| 5 + x));
   |                    ^ not found in this scope

Is it possible to make this work? My understanding was that macros are expanded before compilation.

like image 776
Ivan Avatar asked May 13 '18 17:05

Ivan


People also ask

Are macros allowed in Rust?

The most widely used form of macros in Rust is the declarative macro. These are also sometimes referred to as “macros by example,” “ macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression.

How do closures work in Rust?

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.

When to use macros vs functions Rust?

Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere. Save this answer.

Why are Rust macros useful?

Rust has excellent support for macros. Macros enable you to write code that writes other code, which is known as metaprogramming. Macros provide functionality similar to functions but without the runtime cost. There is some compile-time cost, however, since macros are expanded during compile time.


2 Answers

Peter's answer explains why what you're doing won't work. This is part of what's referred to as "macro hygiene": things declared inside macros can't "leak" into the surrounding scope.

A common workaround for the problem you're facing is to pass the name of the identifier into the macro as another argument:

macro_rules! atest {
    ($x:ident, $closure:tt) => {
        let $x = 5;
        println!("Result is {}", $closure())
    };
}

fn main() {
    atest!(x, (|| 5 + x));
}

This will work because naming x puts it in the caller's scope, even though the declaration is inside the macro.

You might notice that the closure is kind of unnecessary, at least in this example -- you could just pass 5 + x as an expression to the macro and have it expanded inline.

macro_rules! atest {
    ($x:ident, $value:expr) => {
        let $x = 5;
        println!("Result is {}", $value)
    };
}

You call this macro like atest!(x, 5 + x), which looks a little bit like a closure of its own. That might give you the idea of writing atest!(|x| 5 + x) instead. And that will also work, with a variable scoped to the closure:

macro_rules! atest {
    ($closure:expr) => {
        let x = 5;
        println!("Result is {}", $closure(x))
    };
}

References

  • Macros in Rust pt1
like image 82
trent Avatar answered Sep 28 '22 02:09

trent


Is it possible to make this work? My understanding was that macros are expanded before compilation?

Macros are expanded before compilation, but not before parsing. The raw input code has already been parsed and macros operate on an abstract syntax tree, not on text. For example, the closure is already understood to be a closure, and its free variables are already bound to variables in its lexical scope.

This is different to some other languages macros, for example C/C++, which operate on raw text, and let you screw things up pretty badly if you're not careful.

like image 45
Peter Hall Avatar answered Sep 28 '22 01:09

Peter Hall