Understanding the Behavior of Variable Constraints in Macros

Asked 1 years ago, Updated 1 years ago, 444 views

The trustc version is 1.65.0.

The following code has an error and cannot be compiled.

Code of interest:

fnmain(){
    test_macro!{
        12, x, y
    }

    x+y+temp;
}

# [macro_export]
US>macro_rules!test_macro{
    ($e:expr, $x:ident, $y:ident) = > {
        let$x=$e;
        let temp = $e;
        let$y=$e;
    };
}

error messages:

 error [E0425]:cannot find value `temp` in this scope
 -->src/main.rs:6:13
  |
6 | x + y + temp;
  |             ^^^^ not found in this scope

For more information about this error, try `rustc--explain E0425`.
error: could not compile `playground` due to previous error

By the way, if you enter this code and run cargo-expand, you will get the code from which you expanded the macro as follows:

#![feature(prelude_import)]
# [prelude_import]
use std::prelude::rust_2021::*;
# [macro_use]
external crate std;
US>fnmain(){
    let x = 12;
    let temp = 12;
    letty = 12;
    x+y+temp;
}

As far as reading this, temp is visible from the x+y+temp section (similar to x, y.(And this is what I expected.)

Please tell me why temp is not visible in the original code.

trust

2022-11-25 03:15

1 Answers

Rust's declarative macro is healthy (hygienic) for variable names.

A healthy macro (Hygienic macro) is a macro that is guaranteed to avoid the problem of incorrectly capturing identifiers.
—— Healthy Macro-Wikipedia

The variable name introduced in the macro does not conflict with the variable name of the macro caller. (In the sense of Lisp macro, sanitation)
—— How is Rust Macro sanitizing (1/2) Identifier sanitizing

In the example question, the let temp=$e; of the macro expands to let temp=12;, which is different from the temp outside the macro because temp is the identifier written in the macro.You can think of it as automatically changing the name, but cargo-expand doesn't seem to visualize it.

This distinction is similar to the scope created by the block.However, the nested state is different from the visible block, so it seems that the macro-made part is explained that it is painted in a different color.

To understand a healthy macro, each time a macro is deployed, it can be considered that the part developed from the macro is painted in a different color.
Different "color" variables are treated as if they were different names.
—— Programming Rust 2nd Edition 21.4.4 Scope and Healthy Macros

The Little Book of Rust Macros also explains by painting different background colors.

Therefore, if you import the identifier from outside the macro (the user side), the example question will not fail.

fnmain(){
    test_macro!{
        12, x, y, temp
    }

    println!("{}, x+y+temp);
}

# [macro_export]
US>macro_rules!test_macro{
    ($e:expr, $x:ident, $y:ident, $temp_var:ident) = > {
        let$x=$e;
        let$temp_var=$e;
        let$y=$e;
    };
}

Alternatively, I think procedural macros are different, so you may be able to handle identifiers more freely.


2022-11-25 03:54

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.