In this lesson, you will learn how to leverage Rust's macro hygiene system to ensure your generated code behaves predictably, even when users pass names that might conflict with existing variables or external scopes.
The Problem of Variable Capture
When you write a macro_rules! macro, the code it expands into is treated as if it were written exactly where the macro was invoked. A common hazard is "variable capture," where the macro accidentally uses a variable from the surrounding scope instead of a local one, or conversely, where a user-defined variable masks a name the macro expects to be available.
Hygienic Identifiers
Rust’s macro_rules! are "hygienic" by default for local variables. If you define a variable inside a macro, it is scoped to the macro's expansion, preventing it from leaking into the user's scope. However, this hygiene does not extend to items like functions, traits, or structs. If you declare a helper struct inside a macro, it will be visible in the module where the macro is invoked.
To handle this cleanly, use an internal mod to encapsulate your macro's internal implementation details.
macro_rules! create_peripheral_accessor {
($name:ident, $addr:expr) => {
// Use a hidden module to prevent name collisions
mod $name {
pub const ADDR: usize = $addr;
pub fn read() -> u32 { unsafe { *(ADDR as *const u32) } }
}
pub fn $name() -> u32 {
$name::read()
}
};
}
Dealing with External Paths
A common mistake in advanced macros is assuming that common types (like Result or Option) are available in the scope where the macro is invoked. If the user has shadowed Result or imported a different version, your macro might break.
To remain resilient, always use absolute paths or fully qualified paths inside your macros (e.g., ::core::result::Result instead of Result). By using the :: prefix, you ensure the compiler resolves the symbol starting from the crate root, ignoring any local shadowing within the user's function or module.
Key Takeaway: Use internal modules to namespace generated items and fully qualified paths (::path::to::Item) to ignore local shadowing, ensuring your macro's logic remains isolated from the user's environment.