Macros seem to be usable either in a sort-of “direct” manner, in which case, ordering matters, e.g.
fn main() {
let x = 1;
macro_rules! fun {
() => { x };
}
dbg!(fun!());
}
fn main() {
let x = 1;
dbg!(fun!()); // error: cannot find macro `fun` in this scope
macro_rules! fun {
() => { x };
}
}
and you can do things like shadowing:
fn main() {
let x = 1;
macro_rules! fun {
() => { what? };
}
macro_rules! fun {
() => { x };
}
dbg!(fun!());
}
or through the mechanisms of item visibility, like other items such as fn foo
or struct Bar
, which you can get via #[macro_export]
as you did, or with a use
statement, e.g.
fn main() {
let x = 1;
dbg!(fun!()); // error (like above)
macro_rules! fun {
() => { 42 };
}
}
vs
fn main() {
let x = 1;
dbg!(fun!()); // works!
macro_rules! fun {
() => { 42 }; // n.b. we still aren't using `x` here at the moment
}
use fun;
}
where duplicate use
would simply be an error
fn main() {
let x = || 1337;
dbg!(fun!());
macro_rules! fun {
() => { x() };
}
use fun;
dbg!(fun!());
macro_rules! fun {
() => { x() + 1 };
}
use fun; // error[E0252]: the name `fun` is defined multiple times
dbg!(fun!());
}
I’d assume, the behavior is explained by implementation details of the mechanism that allows macros to be used like other items in the first place; items, even if contained within block expressions, are generally independent of the block they are defined in, acting as-if they were implemented as static/global stuff on the module level.
It doesn’t surprise me that a macro that’s made to “behave like other items” like this can end up switching “too” strongly into such a “behave like an item” mode.
Do note, that the behavior here appears to be “resolve x
like an item” i.e. if there's for instance some fn x
, that’ll do, and there’s no compilation error after all.
fn x() -> i32 { 42 }
fn main() {
let x = || 1337;
dbg!(fun!()); // 42
macro_rules! fun {
() => { x() };
}
dbg!(fun!()); // 1337
use fun;
}
It also seems like another macro definition can shadow an external - or use
d - item without problems:
fn x() -> i32 { 42 }
fn main() {
let x = || 1337;
dbg!(fun!()); // 42
macro_rules! fun {
() => { x() };
}
use fun;
dbg!(fun!()); // 1337
macro_rules! fun {
() => { x() + 1 };
}
dbg!(fun!()); // 1338
}
I think this completes the picture above both macro_rules
declarations do also shadow the macro provided via the use
; and it’s also this shadowing why the ordering mattered in your original code:
fn main() {
let x = 1;
dbg!(fun!()); // <- resolves to `fun` available via `#[macro_export]`
#[macro_export]
macro_rules! fun {
() => { x };
}
}
fn main() {
let x = 1;
#[macro_export]
macro_rules! fun { // this macro_rules shadows
() => { x }; // the globally available macro `fun`
} // just like `let x` would shadow some `fn x`
dbg!(fun!());
}
fun
’s definition – for purposes of through local variable-like scoping rules, shadows “itself”.
Is this whole exact behavior desirable? IDK; probably not really. Certainly it could be technically-breaking to change it (but it’s also somewhat realistic that nobody might actually depend on this).
That a top-level
macro_rules! fun {
() => { x };
}
fn main() {
let x = 1;
dbg!(fun!()); // error
}
can never refer to a local variable is also clear; that’s “just hygiene”.
But arguably, the fact that the x
can fall back to referring to a top-level thing seems pretty surprising. Maybe considering this to be turned into a error first can open up space for future changes like making your original code example compile, too.
Without such a restriction, I’d think that alternatives such as making
fn x() -> i32 { 42 }
fn main() {
dbg!(fun!()); // 42
let x = || 1337;
dbg!(fun!()); // 42
macro_rules! fun {
() => { x() };
}
dbg!(fun!()); // 1337
use fun;
}
instead behave like
fn x() -> i32 { 42 }
fn main() {
dbg!(fun!()); // 42
let x = || 1337;
dbg!(fun!()); // 1337 // <- this changed
macro_rules! fun {
() => { x() };
}
dbg!(fun!()); // 1337
use fun;
}
feel very much against proper hygiene, now the resolution of a name in a macro-expansion depends on whether or not a local variable was in scope which (that local variable) wasn’t mentioned in the macro-call. (But instead at the macro definition, but there, it’s always in-scope.)
The current status where the behavior changes with the question of which fun
/ or which way the same fun
/ was imported, might be less bad?
Except… now I’m noticing some more cursed cases – you can rename the macro with use
, and the behavior stays the same
fn x() -> i32 { 42 }
fn main() {
use fun2 as fun3; // a second step of `use` for good measure
// but skippin this second step and using `fun2` below has the same behavior, too
let x = || 1337;
dbg!(fun3!()); // 42
macro_rules! fun { // there's no way this now
() => { x() }; // also *actually shadows* fun2 and fun3, is there!?
}
dbg!(fun3!()); // 1337 // <- still prints 1337 here, vs 42 above..
use fun as fun2;
}
And I keep coming up with interesting test cases. E.g. if this is working with x
being a macro, which is something where hygiene doesn’t apply like for local variables then the behavior is “simply” depending on the new shadowing x
being in scope or not:
fn main() {
dbg!(fun!()); // 42
macro_rules! x {
() => { 1337 }
}
dbg!(fun!()); // 1337
macro_rules! fun {
() => { x!() };
}
use fun;
dbg!(fun!()); // 1337
}
macro_rules! x {
() => { 42 }
}
use x;