The Question
When you first start with Rust, one thing that throws you off is the difference between macros and functions. Coming from Java or Python, this seems odd—why would you need both?
What's the Difference?
Here's the simplest explanation: functions work on data, macros work on code.
In Python, you write:
def add(a, b):
return a + b
In Java, you write:
public static int add(int a, int b) {
return a + b;
}
Both run at runtime—when your program executes.
But in Rust, macros run at compile time—before your code is even compiled. They're code that writes code.
A Simple Example
Function in Rust:
fn add(x: i32, y: i32) -> i32 {
x + y
}
Macro in Rust:
macro_rules! add {
($x:expr, $y:expr) => {
$x + $y
};
}
When you call the macro add!(1, 2), the compiler literally rewrites your code to 1 + 2 before compiling. It's like a smart find-and-replace.
Why Not Just Use Functions?
Macros solve problems that functions can't:
1. Variable Arguments
Ever wonder how println! can take any number of arguments?
println!("Hello");
println!("Hello {}", name);
println!("{} and {}", a, b);
That's because it's a macro. Functions need fixed signatures; macros can accept variable patterns.
2. Code Generation
Python has decorators like @dataclass that generate code for you:
@dataclass
class Person:
name: str
age: int
Java has annotations like @Override or Lombok's @Builder.
In Rust, this is done with macros:
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
The derive macro generates Debug and Clone implementations automatically.
3. Domain-Specific Languages (DSL)
A DSL is a miniature language tailored to a specific problem domain. Think of SQL—it's not a general-purpose language, but a specialized one for databases.
You can create custom syntax with macros:
html! {
<div class="container">
<p>Hello world</p>
</div>
}
This isn't valid Rust code—it's an HTML-like DSL that the macro converts into valid Rust. It lets you write HTML templates directly in your Rust code, which the macro transforms into function calls and data structures at compile time.
Mental Model for Java/Python Developers
| Concept | Python/Java | Rust |
|---|---|---|
| Function | Normal code that runs | Same—runs at runtime |
| Annotation/Decorator | @Override, @dataclass | #[derive(...)] macro |
| Macro | Doesn't really exist | Generates code at compile time |
Think of macros as super-powered annotations. In Java, annotations can generate some code (like Lombok does), but macros can generate any code.
When to Use What?
- Use functions for normal logic and calculations.
- Use macros when you want to avoid boilerplate, create custom syntax, or generate code automatically.
Bottom Line
Macros are Rust's way of reducing repetition and enabling metaprogramming. They run before compilation and rewrite your code. If you're coming from Java or Python, think of them as annotations or decorators on steroids—but they can do much more.
For most beginners, you don't need to write macros. The standard library macros like println!, vec!, and format! will carry you far. But understanding what they are helps when you read Rust code and wonder why you see ! everywhere.
Further Reading
- The Little Book of Rust Macros — A practical guide to writing macros in Rust.
- The Rust Programming Language: Macros — Official Rust book chapter on macros.
- Rust by Example: Macros — Interactive examples to learn macros.
- Domain-Specific Language (DSL) — Wikipedia article explaining the concept.