- Feature Name: hygiene
- Start Date:
- RFC PR:
- Rust Issue:
Hygiene for declarative macros 2.0.
More specifically, names in macro definitions behave independently of
- where the macro is invoked (i.e. in which crate/module/scope)
- what names are used in the macro invocation arguments,
- except when matching against names in patterns.
Today, the only way a macro author can reliably use an item from outside the macro definition
is to employ a $crate::
-prefixed path.
This is cumbersome and doesn't allow macro authors reliably use
- trait items (since the trait methods in scope are not reliable), or
- anything not accessible from outside the crate.
For example, assuming the following hygienic macro foo::m
is invoked in a statement context,
fn f() {} // (i)
pub mod foo {
fn g() {} // (ii)
mod bar {
pub fn h() {} // (iii)
}
trait T {
fn f(&self) {} // (iv)
}
impl T for () {}
pub macro m() {
use super::f; // This always imports (i)
super::f(); // This always resolves to (i)
g(); // This always resolves to (ii)
use self::bar::*; // This always imports (iii)
h(); // This always resolves to (iii)
().f(); // This method call always resolves to (iv)
}
}
Today, a macro author cannot reliably define and use an item; it is always possible for a macro user to make the item to trigger a conflict error.
A reliable definition does not conflict with names from outside the macro definition and has no effect on the resolution of names from outside the macro definition.
For example, assuming the following hygienic macro m
is invoked in a statement context,
macro m($i:ident) {
fn f() {} // (i) This is never a conflict error
f(); // This always resolves to (i)
mod foo {
pub fn $i() {} // (i)
pub fn f() {} // (ii) This is never a conflict error
}
foo::$i(); // This always resolves to (i)
foo::f(); // This always resolves to (ii)
fn g<$x, T /* this is never a conflict error */>(x: ($x, T)) {}
struct S {
$i: u32,
x: i32, // This is never a conflict error
}
impl S {
fn $i(&self) -> u32 { 0 }
fn f(&self) -> i32 { 0 } // This is never a conflict error
}
let s = S { x: 0, $i: 0 };
(s.$i, s.x); // This always has type (u32, i32)
(s.$i(), s.f()); // This always has type (u32, i32)
}
For example, consider
macro m($it:item, $t:ty) {
mod foo {
$it
}
}
fn f() { ... } // If `fn f() { ... }` is a valid item,
m!(fn f() { ... }); // then this should compile.
This is the bulk of the RFC. Explain the design in enough detail for somebody familiar with the language to understand, and for somebody familiar with the compiler to implement. This should get into specifics and corner-cases, and include examples of how the feature is used.
What names and terminology work best for these concepts and why? How is this idea best presented—as a continuation of existing Rust patterns, or as a wholly new one?
Would the acceptance of this proposal change how Rust is taught to new users at any level? How should this feature be introduced and taught to existing Rust users?
What additions or changes to the Rust Reference, The Rust Programming Language, and/or Rust by Example does it entail?
Why should we not do this?
What other designs have been considered? What is the impact of not doing this?
What parts of the design are still TBD?