Step 1 of 6
Your first macro_rules! macro
You've used macros since your first program: println!, format!, vec!, assert_eq!, todo!. A macro is code that writes code. The compiler expands it before type checking, replacing the call with the code the macro produces.
Why have macros at all?
Functions are great, but they have limits that macros don't:
- A function takes a fixed number of arguments with fixed types.
println!takes any number, and checks its format string at compile time. - A function can't create new structs, impls or functions. A macro can generate whole items.
- A function can't
returnfrom its caller. A macro expands inside the caller, so it can.
The price: macros are harder to read, their errors point into generated code, and editors understand them less well. The rule teams follow is simple: if a function (or a generic, or a trait) can do it, write a function. Reach for a macro when it can't.
macro_rules!
Declarative macros are written with macro_rules!. Each rule has a matcher (the pattern the call must look like) and a transcriber (the code it expands to):
macro_rules! square {
($x:expr) => {
$x * $x
};
}
macro_rules! greet {
() => {
println!("Hello, arena!")
};
($name:expr) => {
println!("Hello, {}!", $name)
};
}
fn main() {
println!("{}", square!(7));
println!("{}", square!(2 + 3));
greet!();
greet!("Ferris");
}
49
25
Hello, arena!
Hello, Ferris!
Piece by piece:
($x:expr) => { $x * $x };is one rule.$xis a metavariable, andexpris its fragment specifier: "match one Rust expression here". In the transcriber,$xis replaced by whatever was matched.greet!has two rules. The compiler tries them top to bottom and uses the first one that matches, sogreet!()picks the first andgreet!("Ferris")the second. Put specific rules before general ones.square!(2 + 3)is 25, not 11. The matched expression is pasted in as one unit, as if it had parentheses. (C's#define SQUARE(x) x*xpastes text and gets this wrong; Rust macros work on syntax, not text.)- You can call a macro with any brackets:
square!(7),square![7]andsquare!{7}are the same. By convention,()looks like a function call,[]builds a collection (vec![]), and{}wraps blocks or items. - A
macro_rules!macro must be defined above the code that uses it in the file. Otherwise:cannot find macro `square` in this scope.
Common mistakes
No rule matches the call. The error names the token where matching failed:
macro_rules! greet {
() => {
println!("Hello, arena!")
};
($name:expr) => {
println!("Hello, {}!", $name)
};
}
fn main() {
greet!("Ferris", "Corro");
}
error: no rules expected `,`
Evaluating an argument twice. $x * $x pastes the expression in twice, so it runs twice. That matters when it has side effects or is expensive:
macro_rules! square {
($x:expr) => {
$x * $x
};
}
macro_rules! square_once {
($x:expr) => {{
let v = $x;
v * v
}};
}
fn main() {
let a = square!({ println!("computing"); 3 });
println!("{a}");
let b = square_once!({ println!("computing"); 3 });
println!("{b}");
}
computing
computing
9
computing
9
The fix stores the value in a local first. Notice the double braces {{ ... }}: the outer pair belongs to the rule, the inner pair is a block expression in the expanded code, so let v and v * v become one expression.
Your task
Fill in the blanks of a cube! macro that evaluates its argument once. The program should print 27 and then 8.
Challenge 2
Write max2!(a, b), which evaluates to the larger of two values. Each argument must be evaluated exactly once (a hidden test counts the calls), so store both in locals inside a block before comparing.
Challenge 3
Matchers can contain plain tokens as well as metavariables. Write a log! macro that returns a String:
log!(warn, msg)gives"[WARN] <msg>"log!(error, msg)gives"[ERROR] <msg>"log!(msg)gives"[INFO] <msg>"
msg can be any expression that implements Display. Write the warn and error rules before the general one.