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
185: Declarative Macros with macro_rules! In Depth
How do I actually tell Rust what parts of my macro call are variables and what parts are syntax?
When you're looking at a macro_rules! block, you'll see these strange symbols like $x:expr or $name:ident. Think of these as "matchers." You're essentially telling the Rust compiler, "If you see something that looks like an expression here, capture it and call it $x."
The part after the colon is the designator. If you use expr, it matches almost any valid Rust expression. If you use ident, it only matches a valid identifier (like a function or variable name). I've seen a lot of people default to expr for everything, but that's a mistake. If you're trying to define a new function name inside a macro, expr won't work because a function name isn't an expression—it's an identifier.
macro_rules! create_function {
($name:ident, $body:expr) => {
fn $name() {
println!("Executing: {}", $body);
}
};
}
create_function!(say_hello, "Hello from the macro!");
fn main() {
say_hello();
}
Notice how $name uses ident. If I had used expr there, the compiler would have thrown a fit because you can't have an expression where a function name is required.
How do I handle a variable number of arguments without writing a dozen versions of the macro?
This is where declarative macros actually become powerful. You use repetition patterns: $( ... ) repeated_separator. It looks a bit like regex, and honestly, it's the part of macro_rules! that takes the most getting used to.
Let's say you want a macro that creates a Vec of strings from a list of inputs, but you want to automatically wrap each one in a specific prefix. You don't want to define the macro once for one argument, once for two, and so on. Instead, you do this:
macro_rules! prefix_list {
($prefix:expr, $($item:expr),*) => {
{
let mut v = Vec::new();
$(
v.push(format!("{}{}", $prefix, $item));
)*
v
}
};
}
fn main() {
let items = prefix_list!("ID_", "apple", "banana", "cherry");
// Result: ["ID_apple", "ID_banana", "ID_cherry"]
println!("{:?}", items);
}
Break down that $($item:expr),* part: the $( ... ) is the block to repeat, and the ,* tells Rust that the items are separated by commas, and there can be zero or more of them. If you wanted to require at least one item, you'd use + instead of *. I usually prefer * unless the macro literally cannot function without at least one input.
I keep hearing about "token trees" (tt). When do I actually need those over something like "expr"?
This is the "advanced" level of declarative macros. A tt (Token Tree) is the most basic unit. It matches anything—a single punctuation mark, a keyword, an identifier, or a balanced group of tokens in parentheses.
Why would you want that? Because expr is "greedy." If you're trying to build a complex Domain Specific Language (DSL) inside Rust, expr might gobble up more than you intended, or it might fail because the input isn't a valid Rust expression yet. Using tt allows you to capture raw chunks of code and then pass them into another macro or process them recursively.
I rarely use tt for simple utilities, but it's indispensable when you're building something like a custom HTML-like macro or a sophisticated configuration block where the syntax doesn't map 1:1 to Rust expressions. If you find yourself fighting with the compiler because it says your "expression" is invalid, try switching to tt and see if that lets you capture the tokens you need.
📋 Practical Task
Exercise: Build a "Map Initializer" Macro
One of the most tedious parts of writing tests in Rust is manually inserting a dozen key-value pairs into a HashMap. Your task is to create a declarative macro called map_init! that simplifies this process.
Requirements:
- The macro should take a generic type for the key and value as its first two arguments.
- It should then take a variable number of key-value pairs, where each pair is separated by a
=>symbol, and each pair itself is separated by a comma. - The macro must return a
HashMapcontaining all the provided pairs.
Expected Usage:
let scores = map_init!(&str, i32, "Alice" => 10, "Bob" => 20, "Charlie" => 15);
// This should expand to a HashMap with those three entries.
Hint: You will need to use repetition patterns for the pairs. Think about how to group the key and value together inside the repetition block $( ... ).
There are no comments for now.