Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
186: Macro Hygiene Explained
If you've spent any time with C or C++, you probably have a visceral reaction to the word "macro." In those languages, macros are essentially sophisticated find-and-replace tools. They are "unhygienic," meaning they just dump text into your source code. If your macro defines a variable called tmp and the function using that macro also happens to have a variable called tmp, you've just walked into a naming collision that can take hours to debug.
The chaos of name capture
To understand why hygiene matters, let's imagine Rust's macro_rules! didn't have it. Imagine I wrote a macro to help you calculate the average of a few numbers. Inside the macro, I'd need a variable to hold the running total. In an unhygienic world, it would look something like this:
// WARNING: This is a conceptual "unhygienic" example, NOT how Rust actually works.
macro_rules! calculate_avg {
($($x:expr),*) => {
let total = 0; // Internal variable
$( total += $x; )*
total / count()
};
}
Now, imagine you're using this macro in a function where you've already defined a variable named total to track something entirely different, like your bank balance. The macro would either shadow your balance or, worse, accidentally add your numbers to your bank balance. It's a nightmare because the macro "leaks" its internal implementation details into your local scope, or "captures" your local variables.
How Rust isolates the macro's internals
Rust solves this by making macro_rules! hygienic. When the compiler expands a macro, it doesn't just paste text. It assigns a unique "color" or identity to the variables created inside the macro. Even if you name a variable total inside the macro and the user names a variable total in their function, Rust treats them as two completely different entities.
I'll show you what I mean with a real example. We can define a macro that uses a local variable, and then call it from a scope that uses the exact same name. In any other language, this would be a disaster. In Rust, it's a Tuesday.
macro_rules! sum_and_print {
($($val:expr),*) => {
let accumulator = 0; // This is "hygienic"
$(
accumulator += $val;
)*
println!("The sum is: {}", accumulator);
};
}
fn main() {
let accumulator = 100; // This is a totally different 'accumulator'
sum_and_print!(1, 2, 3);
println!("The original accumulator is still: {}", accumulator);
}
When you run this, the macro doesn't touch the accumulator in main. It creates its own internal version that exists only for the duration of the expansion. You don't have to worry about naming your internal macro variables __my_macro_internal_var_sum_123 just to avoid collisions. You can use clean, descriptive names.
When you actually want to break the wall
Now, here is the trade-off. Hygiene is great until you want the macro to interact with the outside world. For instance, what if your macro needs to call a function that the user provides, or a specific function from a crate that the user has imported?
If the macro refers to Vec::new(), but the user hasn't imported Vec (hypothetically) or has shadowed Vec with their own type, the macro might fail because it's looking for the "macro's version" of Vec, not the "user's version." This is why you'll often see experienced Rust engineers use fully qualified paths inside macros, like ::std::vec::Vec::new(). By using the absolute path starting from the crate root (::), you bypass the ambiguity of the local scope and ensure the macro works regardless of what the user has named their variables or which traits they've imported.
📋 Practical Task
Implementing a Hygienic Guarded-Execution Macro
Your goal is to create a macro called track_execution!. This macro should take a block of code, execute it, and print a message indicating that the block has finished. To test the hygiene, you must ensure that the macro uses an internal variable (e.g., let status = "Done";) and that this variable does not clash with a variable named status in the main function.
Requirements:
- Define
macro_rules! track_executionwhich accepts a block of code{ ... }. - Inside the macro, define a local variable
statusand print it after the block executes. - In
main, define a variablestatuswith a different value (e.g.,"Waiting"). - Call the macro and then print the
mainfunction'sstatusvariable to prove it remained unchanged.
There are no comments for now.