Introduce the $self macro metavariable, a companion to $crate, that allows
macros hygienic access to items.
Motivation
It is presently impossible to define macros with identifiers that resolve at the macro's definition site upon expansion. This shortcoming is well-acknowledged and well-known, and, while declarative macros 2.0 aimed to resolve this issue, its implementation and subsequent stabilization sit in limbo.
As an example of a macro that's presently impossible to write, consider the
following, where PRIVATE is expected to resolve to submod::PRIVATE
regardless of where m is expanded:
As illustrated, the call to the m!() errors as "PRIVATE is not in-scope".
Specifically, the call to m!() expands to println!("{}, PRIVATE);, where
PRIVATE resolves as if it were an item identifier. This implies that the
following does compile, printing Hi! when run, perhaps unexpectedly:
Today, no combination of macro_rules!() or proc_macro invocations embedded
within allows for declaring an m that expands such that PRIVATE in the
expansion resolves to submod::PRIVATE. Even the following example, which
mimics what is possible with identifiers today, fails:
$self resolves this deficiency. With $self, m could be declared as:
On expansion of m, PRIVATE unambiguously and unconditionally resolves as if
it were at the definition site, that is, to submod::PRIVATE.
Guide-level explanation
The $self macro metavariable, like the $crate metavariable, can be used to
modify the hygeine of identifiers in a macro. $self works a lot like the
self in module paths: when used at the start of a path in a macro, the
succeeding path will be resolved as if it were in the module where the macro is
defined, regardless of where the macro is expanded. Different from self in
module paths, however, $self also captures the visibility of the module path
at the definition site: the succeeding path will be visible in the expansion if
it is visible at the macro's definition site.
Said differently, $self captures the module scope at the macro definition
site and applies it to the succeeding path upon expansion. As an example,
consider the definition of the macro submod::m!:
Without $self, it would not be possible to reference submod::PRIVATE outside
of submod. Observe, too, that unlike $crate, $self does have an effect
on visibility: while submod::PRIVATE in main would not resolve, the
expansion including $self::PRIVATE does!
Reference-level explanation
At its core, $self is $crate at the module-level as opposed to the crate
level. Macro metavariable naming collisions are handled in the same way as with
$crate. In particular, a declaration of $self in a macro shadows the $self
described here. The following works as expected, and importantly, as it does
today:
Additionally, like $crate, a non-user-declared $self must be followed by
::.
Notably different is that while $crate can be implemented as a purely
syntactic transformation, substituting $crate for the name of the crate in
which the macro is defined, $self must apply the full resolution context of
the macro's definition site to the succeeding path. When calling a macro using
$self cross-crate, this requires cross-crate hygiene. Thankfully, this was
recently added to the compiler in https://github.com/rust-lang/rust/pull/72121.
Thus, $self can be simply and without further caveats by specified as: for
every path in the expansion that begins with $self, the resolution context of
the path is set to resolution context of the Span::source() of $self.
In addition to the examples in the introductory text, consider the following:
The resulting program prints B, A.
Drawbacks
As always, introducing new language-level features can add the cognitive
overhead. However, $self's similarity to $crate means that it doesn't
introduce an entirely new concept. What's more, it is orthogonal to all existing
language features, which means users find one solution to the problem it
resolves.
$self as described here is backwards-compatible: there are no compatibility
hazards.
Rationale and alternatives
-
Wait for Macros 2.0
Self-explanatory. Unfortunately, the implementation and stabilization of macros 2.0 is in limbo.
-
Propagate Resolution Context for Items, Too
The second
submodexample in the introductory text could be made to work. Unfortunately, this has the major drawback that it breaks existing code. That is, it is not backwards-compatible. Furthermore, it requires two expansions to achieve the same net-effect that this proposal allows in one. -
Use some other syntax, like
#PRIVATE, to capture hygieneInstead of
$self::PRIVATE,#PRIVATEcould yield the same effect. This introduces brand new syntax with no existing analogy, however, and so would be harder to teach.
Prior art
I am not aware of an existing $self-like mechanism in other languages. Rust's
own $crate is the inspiration for this feature. Other issues, notably going
back to https://github.com/rust-lang/rust/issues/22462, have also considered the
deficiency resolved by this proposal.
Unresolved questions
None.
Future possibilities
None. Macros 2.0 continues to be the eventual goal.