This point, and kind of this whole thread, has been mixing the scoping of macros themselves (macro namespacing) with the scoping/resolution of the items referred by the macro expansion (hygiene).
Now back to the OP question of how the macros are, themselves, scoped, and mainly to refer to
Some history / context to explain why your snippet didn't work
The issue you had with your example had to do with "meta-macros", which are, on their own, a quite complex piece of machinery / non-obvious code to implement those in the compiler (macro expansion can define new items that change the meaning of item resolution, and this iterative process needs to be consistent else the compiler bails).
The actual issue in here is indeed the design of #[macro_export] macro_rules!
macros. Those do define, among the programming system, a meta-programming tool that dependent crates may use. At the time the approach was quite consistent with itself (use #[macro_use]
on modules defining local macros, use #[macro_use] extern crate …
for crates defining #[macro_export]
-ed macros). Macros were, at the time, pretty self-consistent, but totally alien / inconsistent with the rest of Rust items.
In an effort to both remove the "redundant" extern crate
annotations, as well as to start unifying the macro namespacing with the namespacing of other items, with the 2018 edition, #[macro_export]
-ed macros were namespaced: rather than doing
#[macro_use]
extern crate lazy_static;
lazy_static! { … }
one could finally do:
::lazy_static::lazy_static! { … }
which means that they could also depend on a crate bar
with its own lazy_static!
macro, and not have to deal with name collisions. Macros no longer being global / un-namespaced items has been a big improvement in that regard.
The issue, here, is that, imho, a mistake was done when implementing this, which becomes clear with the unfair advantage of hindsight: rather than appearing namespaced wherein the #[macro_export]
occurred, these #[macro_export]
ed macros always appeared as namespaced directly at the root of their crate:
At the time it might have made sense, since, inside a crate, macros were still not namespaced at all, and thus it would have been impossible for dependency
to re-export bar
at the root, should they have so wished.
But, this, in term lead to other issues. Consider:
//! dependency/src/lib.rs
#[macro_export]
macro_rules! foo {() => (
bar! {}
)}
#[macro_export]
macro_rules! bar {() => ()}
Because of the lack of proper hygiene, if a dependent were to call ::dependency::foo!()
, they'd stumble upon a 'bar!' not found
error. So a #[macro_export(local_inner_macros)]
hack was created, but since these hacks still suffered from the "single global namespace for all macros in the universe", in order to follow the new trend of namespacing, $crate::
was created, allowing the above to be written as:
//! dependency/src/lib.rs
#[macro_export]
macro_rules! foo {() => (
- bar! {}
+ $crate::bar! {}
)}
#[macro_export]
macro_rules! bar {() => ()}
This solved the issue of cross-crate calls, but then we had the problem of a local call to foo!
failing because:
$crate::
wasn't really defined, although it could (and does) fallback to crate
.
crate::bar!
wasn't defined, since, locally, macros were never namespaced.
In order to palliate this, it was made so #[macro_export]
macros would be namespaced from within the crate defining it itself (first time this occurred), and, for the sake of consistency with $crate::
paths, they had to be namespace-moved / teleported to the root of the crate:
mod module {
foo!(); // Error, no `foo!` in scope
crate::foo!(); // OK
#[macro_export]
macro_rules! foo {() => ()}
}
Here is where we can see that having namespaced macros at the root of the crate wasn't ideal to begin with.
Enter meta-macros
Prelude: name-resolution loop
Since macros have always been able to define new items that thus affected name resolution, there is this back-and-forth between the meta-programming system (macro expansion) and the programming / compiler pass (in charge of name / path resolution).
Indeed, consider:
// first pass: resolves `unstringify` and `module::fun`.
use ::unstringify::unstringify;
use self::module::*;
mod module { pub fn fun() {} }
fun(); // first pass: this seems to refer to `module::fun`
// ^^^ second pass: this actually refers to the function from macro expansion.
// +++-----------------------------------+
// |
unstringify!("fn fun() {}"); // ----+
// when this is expanded, it shadows
// the previous `fun`
So far so good, but this now is problematic with
#[macro_export]
Indeed, those are able to affect the namespace of the root of the crate from within arbitrarily nested modules! Combine that with the above interactive loop, and you get a big mess for compiler authors.
Hence why it was decided that the only way to occupy the "root of the crate" was through an explicit #[macro_export]
, and not through a macro-expanded one / through one obtained from macro expansion. Hence that error about "name resolution is stuck", or the one about macro-expanded-macro-export-macro-referred-by-absolute-path. The solution is then to use, inside that crate, a name resolution path that does not involve the one at the root of the crate, such as:
mod macros {
#[macro_export]
macro_rules! m {() => ()}
pub use m; // OK, "old/non-namespaced" path
}
macros::m!(); // OK