011 - Macros in Rust
Learning Rust as a Pythonista: Macros in Rust
Rust macros are metaprogramming tools: they let you generate code at compile time. For Python developers, the closest comparison is decorators and runtime metaprogramming—but Rust macros run earlier (during compilation), so they can remove boilerplate with no runtime overhead.
Why this matters for Python developers
- Python decorators modify behavior at runtime.
- Rust macros shape code at compile time.
- This is useful when you want less repetition without giving up performance.
Learning goals
By the end of this lesson, you should be able to:
- Explain when to use a function vs a macro.
- Write simple
macro_rules!macros with arguments and repetition. - Recognize when procedural macros are overkill for beginners.
Concepts in 5 minutes
macro_rules!= declarative macro system based on pattern matching.- Macros expand before normal type checking.
- Good use cases: tiny DSL-like syntax, repetitive declarations, ergonomics wrappers.
Python baseline snippet
def trace(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper
@trace
def greet(name):
print(f"Hi {name}")
greet("Rust")
Rust equivalent snippets
1) A minimal macro
macro_rules! say_hello {
() => {
println!("Hello, Rust!");
};
}
2) Macro with arguments
macro_rules! print_value {
($val:expr) => {
println!("Value: {}", $val);
};
}
3) Repetition pattern
macro_rules! create_functions {
($($name:ident),* $(,)?) => {
$(
fn $name() {
println!("You called: {}", stringify!($name));
}
)*
};
}
One runnable end-to-end example
macro_rules! say_hello {
() => {
println!("Hello, Rust!");
};
}
macro_rules! print_value {
($val:expr) => {
println!("Value: {}", $val);
};
}
macro_rules! create_functions {
($($name:ident),* $(,)?) => {
$(
fn $name() {
println!("You called: {}", stringify!($name));
}
)*
};
}
create_functions!(foo, bar, baz);
fn main() {
say_hello!();
print_value!(42);
print_value!("Hello, world!");
foo();
bar();
baz();
}
Procedural macros (optional appendix)
Procedural macros (derive, attribute-like, function-like) are powerful but usually a separate crate/topic. For this learning path, treat them as advanced material after you are comfortable with traits and crate structure.
Start by using common derive macros first:
#[derive(Debug, Clone)]
struct Point {
x: i32,
y: i32,
}
Common mistakes
- Using a macro where a normal function is simpler.
- Writing macros before understanding ownership and traits.
- Making macro error messages too cryptic for future readers.
Quick practice
- Create a macro
sum_two!(a, b)that printsa + b. - Create a macro
make_getters!(field1, field2, ...)that generates simple getter functions.
Recap
Use macro_rules! to reduce repetitive syntax when functions are not enough. Keep it simple, readable, and focused.
In the next step of your Rust journey, revisit your earlier lessons and identify one repeated pattern you can clean up with a small macro.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Learning Rust as a Pythonista: A Suggested Path
- 001 - Your First Rust Program
- 002 - Basic Syntax and Structure
- 003 - Ownership, Borrowing, and Lifetimes
- 004 - Error Handling
- 005 - Structs and Enums
- 006 - Iterators and Closures
- 007 - Traits vs Duck Typing and Protocols
- 008 - Concurrency in Rust for Python Developers
- 009 - Async Concurrency with Tokio
- 010 - Pattern Matching in Rust