Skip to content
Learn Rust
Theme

Macros you'll read

Going further · Lesson 51

A macro is code that writes code. The compiler runs it, puts what it produced where the call was, and only then checks types. You met the macros you call every day in Macros, attributes and naming. This page is for reading the others: a macro_rules! block in a dependency, a #[derive(...)] that writes code you never see, an attribute such as #[tokio::main]. You will read far more macros than you write.

Coming from GoGo has no macros. map[string]int{...} is syntax the language provides. Rust has no map literal, and the hashmap! macro below fills the gap. go generate is the nearest idea: it runs a program you wrote and leaves source files behind. A Rust macro runs inside the compiler whenever the crate is compiled and leaves nothing in your source tree.

Coming from KotlinKotlin has no macros either: mapOf("a" to 1) is an ordinary function. Code generators for Kotlin, such as KSP, are compiler plugins. Rust's derive and attribute macros fill that role, and the ones you add yourself (serde's, for example) arrive as a crate in Cargo.toml.

Coming from PythonPython has no macro stage: ordinary code cannot rewrite other code before it runs. @dataclass rewrites a class when the module is imported; #[derive] writes its code while the program compiles, so a mistake is reported before anything runs.

Coming from TypeScriptTypeScript has no macros. A Babel plugin is the nearest mechanism: it rewrites code in a build step, outside the language, for the whole build. Decorators run when the program runs. A Rust macro is part of the compiler's own pipeline and runs only where the code names it.

Coming from JavaAn annotation is metadata, and an annotation processor reads it inside javac while your code compiles and generates code. #[derive] and attribute macros are the nearest match, and macro_rules! has no Java counterpart at all.

Coming from C#A source generator runs inside the C# compiler and adds code, as a derive macro does. A macro_rules! macro has no C# counterpart, and [CallerArgumentExpression] covers one small job macros do, capturing the text of an argument.

Coming from CA #define substitutes tokens before the compiler parses anything, which is where SQUARE(x) x*x goes wrong. macro_rules! works on parsed pieces of code (an expr, a ty), and the variables a macro makes do not collide with yours.

Coming from C++Templates and constexpr do much of what you would reach for a macro to do, and Rust's generics do the same jobs. What templates cannot do (generate a function whose name you pass in, capture an expression's text) is the work of macros.

The quick comparison

Rustsrc/main.rsRun ↗
use std::collections::HashMap;
// A macro: a pattern on the left of `=>`, the code it becomes on the right.
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = HashMap::new();
$(map.insert($key, $value);)*
map
}};
}
fn main() {
let ages = hashmap! { "Ada" => 36, "Linus" => 28, "Grace" => 45 };
let mut names: Vec<_> = ages.keys().collect();
names.sort();
for name in names {
println!("{name}: {}", ages[name]);
}
println!("entries: {}", ages.len());
}
$ cargo run
Ada: 36
Grace: 45
Linus: 28
entries: 3
package main
import (
"fmt"
"sort"
)
func main() {
// A map literal is part of the language: no macro needed
ages := map[string]int{"Ada": 36, "Linus": 28, "Grace": 45}
names := make([]string, 0, len(ages))
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s: %d\n", name, ages[name])
}
fmt.Println("entries:", len(ages))
}
$ go run main.go
Ada: 36
Grace: 45
Linus: 28
entries: 3
fun main() {
// mapOf is an ordinary function; `to` makes a pair
val ages = mapOf("Ada" to 36, "Linus" to 28, "Grace" to 45)
for (name in ages.keys.sorted()) {
println("$name: ${ages[name]}")
}
println("entries: ${ages.size}")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
Ada: 36
Grace: 45
Linus: 28
entries: 3
# A dict literal is part of the language: no macro needed
ages = {"Ada": 36, "Linus": 28, "Grace": 45}
for name in sorted(ages):
print(f"{name}: {ages[name]}")
print(f"entries: {len(ages)}")
$ python3 main.py
Ada: 36
Grace: 45
Linus: 28
entries: 3
// Object and Map literals are part of the language: no macro needed
const ages = new Map([
["Ada", 36],
["Linus", 28],
["Grace", 45],
]);
for (const name of [...ages.keys()].sort()) {
console.log(`${name}: ${ages.get(name)}`);
}
console.log(`entries: ${ages.size}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
Ada: 36
Grace: 45
Linus: 28
entries: 3
import java.util.Map;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
// Map.of is an ordinary method; the language has no map literal
Map<String, Integer> ages = Map.of("Ada", 36, "Linus", 28, "Grace", 45);
for (String name : new TreeSet<>(ages.keySet())) {
System.out.println(name + ": " + ages.get(name));
}
System.out.println("entries: " + ages.size());
}
}
$ javac Main.java && java Main
Ada: 36
Grace: 45
Linus: 28
entries: 3
// A collection initializer is part of the language: no macro needed
var ages = new Dictionary<string, int> { ["Ada"] = 36, ["Linus"] = 28, ["Grace"] = 45 };
foreach (var name in ages.Keys.Order())
{
Console.WriteLine($"{name}: {ages[name]}");
}
Console.WriteLine($"entries: {ages.Count}");
$ dotnet run
Ada: 36
Grace: 45
Linus: 28
entries: 3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// C has no map type and no macro-free literal for one: an array of pairs
struct Entry {
const char *name;
int age;
};
static int by_name(const void *a, const void *b) {
return strcmp(((const struct Entry *)a)->name, ((const struct Entry *)b)->name);
}
int main(void) {
struct Entry ages[] = {{"Ada", 36}, {"Linus", 28}, {"Grace", 45}};
size_t count = sizeof ages / sizeof ages[0];
qsort(ages, count, sizeof ages[0], by_name);
for (size_t i = 0; i < count; i++) {
printf("%s: %d\n", ages[i].name, ages[i].age);
}
printf("entries: %zu\n", count);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
Ada: 36
Grace: 45
Linus: 28
entries: 3
#include <iostream>
#include <map>
#include <string>
int main() {
// An initializer list is part of the language: no macro needed
std::map<std::string, int> ages{{"Ada", 36}, {"Linus", 28}, {"Grace", 45}};
for (const auto &[name, age] : ages) { // std::map iterates in key order
std::cout << name << ": " << age << "\n";
}
std::cout << "entries: " << ages.size() << "\n";
return 0;
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
Ada: 36
Grace: 45
Linus: 28
entries: 3

What's different

  • A name followed by ! is a macro call, and it may use (), [] or {}. The rules of the macro decide what may go inside, so vec![1, 2] and hashmap! { "a" => 1 } are different grammars.
  • macro_rules! is a list of rules, each (pattern) => { code }. To read a call, find the first rule whose pattern fits, then swap the pieces the pattern captured into the code.
  • $x:expr captures one expression, $name:ident a name, $t:ty a type, and $( ... ),* repeats whatever is inside it, separated by commas.
  • Derives and attributes such as #[tokio::main] are macros too. Those from a crate are Rust functions that read and write tokens, in a crate of their own.
  • A function is the right tool until it is not. A macro is for what a function cannot do: take a list of any length, generate items, capture the text of an expression.
Gotcha

A macro call has the syntax its author gave it

Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.
Rustsrc/main.rs✗ won't compile
use std::collections::HashMap;
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = HashMap::new();
$(map.insert($key, $value);)*
map
}};
}
fn main() {
let ages = hashmap! { "Ada": 36, "Linus": 28 };
println!("entries: {}", ages.len());
}
package main
import "fmt"
func main() {
ages := map[string]int{"Ada": 36, "Linus": 28}
fmt.Println("entries:", len(ages))
}
$ go run main.go✓ runs
entries: 2
fun main() {
val ages = mapOf("Ada" to 36, "Linus" to 28)
println("entries: ${ages.size}")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
entries: 2
ages = {"Ada": 36, "Linus": 28}
print("entries:", len(ages))
$ python3 main.py✓ runs
entries: 2
const ages = { Ada: 36, Linus: 28 };
console.log("entries:", Object.keys(ages).length);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
entries: 2
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> ages = Map.of("Ada", 36, "Linus", 28);
System.out.println("entries: " + ages.size());
}
}
$ javac Main.java && java Main✓ runs
entries: 2
var ages = new Dictionary<string, int> { ["Ada"] = 36, ["Linus"] = 28 };
Console.WriteLine($"entries: {ages.Count}");
$ dotnet run✓ runs
entries: 2
#include <stdio.h>
struct Entry {
const char *name;
int age;
};
int main(void) {
struct Entry ages[] = {{"Ada", 36}, {"Linus", 28}};
printf("entries: %zu\n", sizeof ages / sizeof ages[0]);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
entries: 2
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> ages{{"Ada", 36}, {"Linus", 28}};
std::cout << "entries: " << ages.size() << "\n";
return 0;
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
entries: 2
What does the compiler say? Guess first, then open.
$ cargo build
error: no rules expected `:`
  --> src/main.rs:12:32
   |
 3 | macro_rules! hashmap {
   | -------------------- when calling this macro
...
12 |     let ages = hashmap! { "Ada": 36, "Linus": 28 };
   |                                ^ no rules expected this token in macro call
   |
note: while trying to match `=>`
  --> src/main.rs:4:18
   |
 4 |     ($($key:expr => $value:expr),* $(,)?) => {{
   |                  ^^
What happens in Go

Go runs this. A map literal is part of the language, and key: value is how its entries are written.

What happens in Kotlin

Kotlin runs this, but the pairs are written "Ada" to 36: mapOf is a function and to makes a pair. There is no colon form.

What happens in Python

Python runs this. A dict literal is part of the language, with the colon.

What happens in TypeScript

TypeScript runs this. An object literal takes key: value. A Map has no literal and takes an array of pairs instead.

What happens in Java

Java runs this, with Map.of("Ada", 36, ...), an ordinary method that takes keys and values in turn. Java has no map literal.

What happens in C#

C# runs this. A collection initializer is part of the language and takes ["Ada"] = 36 entries.

What happens in C

C runs this, with an array of structs: the standard library has no map type to write a literal for.

What happens in C++

C++ runs this. A std::map takes a list of {key, value} pairs through an initializer list.

What Rust sees

hashmap! is not part of the language. It has one rule, and that rule says each pair is key => value. A : fits no rule, so the compiler stops at it.

Why Rust says no

The macro's own patterns decide what a call may look like. The error names the token that fits no rule and points at what the macro was trying to match when it gave up. When you meet a macro you do not know, its documentation or its macro_rules! block is where the grammar is written.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
use std::collections::HashMap;
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = HashMap::new();
$(map.insert($key, $value);)*
map
}};
}
fn main() {
let ages = hashmap! { "Ada" => 36, "Linus" => 28 };
println!("entries: {}", ages.len());
}
$ cargo run
entries: 2

The toolkit

Macros, attributes and naming taught you to call macros such as println! and vec!. This page is for reading the others: a macro_rules! block in a dependency, the #[derive(...)] above a struct, the #[tokio::main] above main. The aim is to open one, say what it accepts and what it becomes, and know when a function would do. Outputs: Linux on 64-bit ARM, Rust 1.99.1, a debug build.

A macro runs inside the compiler. It reads the tokens of your call (the words, numbers and symbols of source, such as x, + or () and writes new tokens in place of the call. The Book says “macros are expanded before the compiler interprets the meaning of the code” (chapter 20.5), so a macro can produce what a function cannot, such as a trait implementation.

Four shapes of macro

What you see What it is Examples
name!(...), name![...], name!{...} Declarative: macro_rules!, written in ordinary Rust vec!, println!
the same Procedural, function-like: a Rust function over tokens, in a crate of its own make_answer! in the procedural section
#[derive(Name)] Derive: a procedural macro; the built-in ones ship with the compiler Debug, serde’s Serialize (serde)
#[name], #[path::name(...)] Attribute: a procedural macro, or built into the compiler #[tokio::main] (tokio), #[test]

To tell which you are looking at:

  • A ! after a name is a function-like macro, and the brackets are not an array or an index: vec![1, 2] is a call. What it accepts is written in its docs or its macro_rules! block (vec! shows its declaration).
  • #[derive(A, B)] calls one derive macro per name.
  • Another #[...] is built into the compiler (#[inline], #[cfg(...)], #[test]) or an attribute macro from a crate, which a path such as tokio::main gives away.
  • An attribute inside a derived type, such as #[serde(rename = "x")] or #[default], is a helper attribute: the derive reads it.

Reading a macro_rules! block

A macro_rules! block is a list of rules, like the arms of a match, except the patterns match source code. Each rule is (pattern) => { code }. This one follows std’s vec! without its [x; n] form:

Rustsrc/main.rsRun ↗
// The rules of std's vec!, without the `[x; n]` form
macro_rules! my_vec {
() => {
Vec::new()
};
($($x:expr),+ $(,)?) => {{
let mut v = Vec::new();
$(v.push($x);)+
v
}};
}
fn main() {
let empty: Vec<i32> = my_vec![];
let nums = my_vec![1, 2 + 3, 4,];
let words = my_vec!["a", "b"];
println!("empty: {empty:?}");
println!("nums: {nums:?}");
println!("words: {words:?}");
}
$ cargo run
empty: []
nums: [1, 5, 4]
words: ["a", "b"]

To read a call, find the first rule whose pattern fits. my_vec![] fits the first, Vec::new(). For my_vec![1, 2 + 3, 4,] the second fits:

  • $x:expr captures one whole expression, so 2 + 3 is one capture, not three tokens.
  • $( ... ),+ means “one or more, separated by commas”; * is zero or more and ? zero or one, so $(,)? allows a trailing comma.
  • $(v.push($x);)+ is written once per capture.
  • The doubled braces: the outer pair delimits the code, the inner pair makes a block, so the expansion is one expression.

The $name:kind part is a fragment specifier, the kind of code to capture (all of them).

Specifier Captures Example
expr an expression 1 + 2 * x.len()
ident a name total
ty a type Vec<&str>
pat a pattern Some(1 | 2)
literal a literal, with an optional - -3.5
tt one token, or one bracketed group + or (b, c)
block stmt item path lifetime meta vis rarer: look them up -
Rustsrc/main.rsRun ↗
macro_rules! show_expr {
($e:expr) => {
stringify!($e)
};
}
macro_rules! show_ident {
($i:ident) => {
stringify!($i)
};
}
macro_rules! show_ty {
($t:ty) => {
stringify!($t)
};
}
macro_rules! show_pat {
($p:pat) => {
stringify!($p)
};
}
macro_rules! show_literal {
($l:literal) => {
stringify!($l)
};
}
macro_rules! count_tt {
($($t:tt)*) => {
[$(stringify!($t)),*].len()
};
}
fn main() {
println!("expr: {}", show_expr!(1 + 2 * x.len()));
println!("ident: {}", show_ident!(total));
println!("ty: {}", show_ty!(Vec<&str>));
println!("pat: {}", show_pat!(Some(1 | 2)));
println!("literal: {}", show_literal!(-3.5));
println!(
"tt count of `a + (b, c) [d] 4`: {}",
count_tt!(a + (b, c) [d] 4)
);
}
$ cargo run
expr:    1 + 2 * x.len()
ident:   total
ty:      Vec<&str>
pat:     Some(1 | 2)
literal: -3.5
tt count of `a + (b, c) [d] 4`: 5

$($t:tt)* takes any tokens and hands them on untouched, which is how println! passes its arguments on. Its second rule is in the std source (macros.rs): ($($arg:tt)*) => {{ $crate::io::_print($crate::format_args_nl!($($arg)*)); }}. $crate stands for the crate the macro was written in, so the expansion works wherever it is called.

Other things you will see

You see It means
#[macro_export] The macro is available from the crate’s root path, so other crates can use it.
A leading ::, as in ::std::collections::HashMap An absolute path: not something of the same name in the caller’s scope.
A pattern starting with @inner An internal rule, by convention: the macro calls itself with that marker. Read the public rules first (Little Book of Rust Macros).

Seeing an expansion

rustc -Zunpretty=expanded prints code after expansion, the gcc -E of Rust. Unlike gcc -E, only the nightly compiler accepts the flag. RUSTC_BOOTSTRAP=1 makes the stable compiler accept it, as the program below shows. That is an escape hatch for reading code in a scratch directory: the rustc developer guide says the variable “should never be used except when bootstrapping the compiler” (guide), so keep it out of a project you ship. cargo expand wraps the same flag; it is not installed in the runner, so it is not run. The Rust in the program is only a harness; read the output.

Rustsrc/main.rsRun ↗
use std::process::{Command, Output};
const SOURCE: &str = r#"use std::collections::HashMap;
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = HashMap::new();
$(map.insert($key, $value);)*
map
}};
}
macro_rules! sum {
($x:expr) => {
$x
};
($head:expr, $($tail:expr),+) => {
$head + sum!($($tail),+)
};
}
fn main() {
let ages = hashmap! { "Ada" => 36, "Linus" => 28 };
let total = sum!(1, 2, 3);
println!("{} entries, total {total}", ages.len());
}
"#;
// The Rust below is only a harness: it saves SOURCE and asks rustc for the expansion.
// -Zunpretty=expanded is a nightly flag; RUSTC_BOOTSTRAP=1 makes this stable compiler accept it.
fn rustc(bootstrap: bool) -> Output {
let path = std::env::temp_dir().join("expand-demo.rs");
std::fs::write(&path, SOURCE).unwrap();
let mut command = Command::new("rustc");
command
.args(["--edition", "2024", "-Zunpretty=expanded"])
.arg(&path);
if bootstrap {
command.env("RUSTC_BOOTSTRAP", "1");
}
let out = command.output().unwrap();
std::fs::remove_file(&path).unwrap();
out
}
fn main() {
let refused = String::from_utf8_lossy(&rustc(false).stderr).into_owned();
println!(
"without RUSTC_BOOTSTRAP: {}",
refused.lines().next().unwrap()
);
let text = String::from_utf8_lossy(&rustc(true).stdout).into_owned();
// The macro definitions stay in the output; the interesting part is `main`.
let main_at = text.find("fn main()").unwrap();
println!(
"with RUSTC_BOOTSTRAP=1, from `fn main`:\n{}",
&text[main_at..]
);
}
$ cargo run
without RUSTC_BOOTSTRAP: error: the option `Z` is only accepted on the nightly compiler
with RUSTC_BOOTSTRAP=1, from `fn main`:
fn main() {
    let ages =
        {
            let mut map = HashMap::new();
            map.insert("Ada", 36);
            map.insert("Linus", 28);
            map
        };
    let total = 1 + (2 + 3);
    {
        ::std::io::_print(format_args!("{0} entries, total {1}\n", ages.len(),
                total));
    };
}

hashmap! became insert calls in a block. sum!(1, 2, 3) became 1 + (2 + 3): the second rule’s $head + sum!(...) was expanded again for the tail. println! became a call to ::std::io::_print with a format_args!, as its rules said, and {total} became argument 1.

Hygiene

Macros, attributes and naming showed that a macro’s variable does not collide with the caller’s. The Reference calls this “mixed-site hygiene”: locals and labels belong to where the macro was written. So a macro’s locals are hidden from the caller:

Rustsrc/main.rs✗ won't compile
macro_rules! make {
() => {
let local = 5;
};
}
fn main() {
make!();
println!("local = {}", local);
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find value `local` in this scope
 --> src/main.rs:9:28
  |
9 |     println!("local = {}", local);
  |                            ^^^^^ not found in this scope
  |
help: an identifier with the same name is defined here, but is not accessible due to macro hygiene
 --> src/main.rs:3:13
  |
3 |         let local = 5;
  |             ^^^^^
...
8 |     make!();
  |     ------- in this macro invocation
  = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)

Why a macro and not a function

A function signature “must declare the number and type of parameters the function has” (the Book):

Rustsrc/main.rs✗ won't compile
fn sum(values: &[i32]) -> i32 {
values.iter().sum()
}
fn main() {
println!("{}", sum(&[1, 2, 3]));
println!("{}", sum(1, 2, 3));
}
$ cargo buildWhat is E0061?
error[E0061]: this function takes 1 argument but 3 arguments were supplied
 --> src/main.rs:7:20
  |
7 |     println!("{}", sum(1, 2, 3));
  |                    ^^^ -  -  - unexpected argument #3 of type `{integer}`
  |                        |  |
  |                        |  unexpected argument #2 of type `{integer}`
  |                        expected `&[i32]`, found integer
  |
note: function defined here
 --> src/main.rs:1:4
  |
1 | fn sum(values: &[i32]) -> i32 {
  |    ^^^ --------------
help: remove the extra arguments
  |
7 -     println!("{}", sum(1, 2, 3));
7 +     println!("{}", sum(/* &[i32] */));

A function can take a slice, as sum(&[1, 2, 3]) does. A macro makes the call itself look like a list, with a type per argument, as in println!. A macro also sees the code, not only its value, which gives it five powers:

A macro can Shown in
take a list of any length, written as a call my_vec!, sum! above
take a name or type and generate items from it the first program below
capture the text of an expression the second program below
return from the function that called it the third program below
check its input while compiling: println! rejects a format string that does not fit its arguments Macros, attributes and naming
Rustsrc/main.rsRun ↗
trait Describe {
fn describe(&self) -> String;
}
// One impl block for each type in the list
macro_rules! describe_all {
($($t:ty),+ $(,)?) => {
$(impl Describe for $t {
fn describe(&self) -> String {
format!("{} {}", stringify!($t), self)
}
})+
};
}
describe_all!(i32, u8, f64, bool);
// A function whose name the caller chooses
macro_rules! make_adder {
($name:ident, $n:expr) => {
fn $name(x: i32) -> i32 {
x + $n
}
};
}
make_adder!(add_one, 1);
make_adder!(add_ten, 10);
fn main() {
println!("{}", 7_i32.describe());
println!("{}", 7_u8.describe());
println!("{}", 2.5_f64.describe());
println!("{}", true.describe());
println!("add_one(5) = {}, add_ten(5) = {}", add_one(5), add_ten(5));
}
$ cargo run
i32 7
u8 7
f64 2.5
bool true
add_one(5) = 6, add_ten(5) = 15

describe_all! wrote four impl blocks and make_adder! two functions named by the caller.

Rustsrc/main.rsRun ↗
macro_rules! show {
($e:expr) => {
println!("{} = {:?}", stringify!($e), $e)
};
}
// rustfmt would respace `a+b`, which is the point of the second line
#[rustfmt::skip]
fn main() {
let (a, b) = (1, 2);
show!(a + b);
show!(a+b);
show!(a * (b + 3));
show!(vec![a, b].len());
}
$ cargo run
a + b = 3
a+b = 3
a * (b + 3) = 5
vec![a, b].len() = 2

stringify! turns tokens into text, as assert! does for its message. The std docs warn that “the expanded results of the input tokens may change in the future”, so use it for messages, not logic.

Rustsrc/main.rsRun ↗
macro_rules! parse_or_zero {
($s:expr) => {
match $s.parse::<i32>() {
Ok(n) => n,
Err(_) => return 0,
}
};
}
fn sum_pair(a: &str, b: &str) -> i32 {
let x = parse_or_zero!(a);
let y = parse_or_zero!(b);
println!(" both parsed: {x} and {y}");
x + y
}
fn main() {
println!("sum_pair(\"1\", \"2\") = {}", sum_pair("1", "2"));
println!("sum_pair(\"1\", \"x\") = {}", sum_pair("1", "x"));
}
$ cargo run
  both parsed: 1 and 2
sum_pair("1", "2") = 3
sum_pair("1", "x") = 0

The return inside parse_or_zero! leaves sum_pair, so the second call prints no “both parsed” line. For errors, ? is the usual answer (Errors).

The Book says macro definitions are “generally more difficult to read, understand, and maintain than function definitions”. And a macro pastes your argument wherever $x appears, so it can run twice. A function evaluates it once:

Rustsrc/main.rsRun ↗
use std::cell::Cell;
macro_rules! square_twice {
($x:expr) => {
$x * $x
};
}
macro_rules! square_once {
($x:expr) => {{
let v = $x;
v * v
}};
}
fn square(x: i32) -> i32 {
x * x
}
fn main() {
let calls = Cell::new(0);
let next = || {
calls.set(calls.get() + 1);
calls.get()
};
let a = square_twice!(next());
println!("square_twice!(next()) = {a} after {} calls", calls.get());
calls.set(0);
let b = square_once!(next());
println!("square_once!(next()) = {b} after {} calls", calls.get());
calls.set(0);
let c = square(next());
println!("square(next()) = {c} after {} calls", calls.get());
}
$ cargo run
square_twice!(next()) = 2 after 2 calls
square_once!(next())  = 1 after 1 calls
square(next())        = 1 after 1 calls

When a macro call fails

A call fits no rule, or the code the rule wrote does not compile.

Rustsrc/main.rs✗ won't compile
macro_rules! my_vec {
() => {
Vec::new()
};
($($x:expr),+ $(,)?) => {{
let mut v = Vec::new();
$(v.push($x);)+
v
}};
}
fn main() {
let v = my_vec![0; 3];
println!("{v:?}");
}
$ cargo build
error: no rules expected `;`
  --> src/main.rs:13:22
   |
 1 | macro_rules! my_vec {
   | ------------------- when calling this macro
...
13 |     let v = my_vec![0; 3];
   |                      ^ no rules expected this token in macro call
   |
   = note: while trying to match sequence start

“No rules expected ;” names the first token that fits nothing: this my_vec! has no [x; n] rule, where std’s does. In the gotcha the note said “while trying to match =>”, what the rule wanted next. Read the message against the macro_rules! block or the docs.

Rustsrc/main.rs✗ won't compile
struct Age(u32);
macro_rules! age {
($years:expr) => {
Age($years)
};
}
fn main() {
let ada = age!(36);
let linus = age!("twenty-eight");
println!("{} {}", ada.0, linus.0);
}
$ cargo buildWhat is E0308?
error[E0308]: mismatched types
  --> src/main.rs:11:22
   |
 5 |         Age($years)
   |         --- arguments to this struct are incorrect
...
11 |     let linus = age!("twenty-eight");
   |                      ^^^^^^^^^^^^^^ expected `u32`, found `&str`
   |
note: tuple struct defined here
  --> src/main.rs:1:8
   |
 1 | struct Age(u32);
   |        ^^^

This one fit a rule and failed later: an ordinary type error, E0308, pointing at your argument and at the line inside the macro that received it. To see what the macro wrote, expand it as above.

Going deeper

Why hashmap! uses =>

The gotcha gave hashmap! a :. A rule with $key:expr cannot accept it: after an expr fragment only =>, , and ; may follow (Reference, “follow-set ambiguity restrictions”).

Rustsrc/main.rs✗ won't compile
// The gotcha's call used `"Ada": 36`. Can a rule be written to accept it?
macro_rules! pair {
($key:expr : $value:expr) => {
($key, $value)
};
}
fn main() {
let p = pair!("Ada": 36);
println!("{p:?}");
}
$ cargo build
error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments
 --> src/main.rs:3:16
  |
3 |     ($key:expr : $value:expr) => {
  |                ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

A literal key has no such limit, so another macro could take "Ada": 36. This one takes any expression as a key, so it uses =>.

Derive macros: what the one line writes

#[derive(Debug, Clone, PartialEq)] is a macro call. It reads the struct and writes impl blocks. This is the code after the derives ran, for a struct and for an enum with a helper attribute:

Rustsrc/main.rsRun ↗
use std::process::Command;
const POINT: &str = "#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
";
const MODE: &str = "#[derive(Default)]
enum Mode {
#[default]
Fast,
Slow,
}
";
// A harness: it asks rustc (RUSTC_BOOTSTRAP=1, as above) for the source after macros ran.
fn expand(source: &str) -> String {
let path = std::env::temp_dir().join("derive-demo.rs");
std::fs::write(&path, source).unwrap();
let out = Command::new("rustc")
.args([
"--edition",
"2024",
"--crate-type",
"lib",
"-Zunpretty=expanded",
])
.arg(&path)
.env("RUSTC_BOOTSTRAP", "1")
.output()
.unwrap();
std::fs::remove_file(&path).unwrap();
let text = String::from_utf8_lossy(&out.stdout).into_owned();
// Skip the prelude lines every expansion starts with.
text.lines().skip(4).collect::<Vec<_>>().join("\n")
}
fn main() {
println!("--- you write ---\n{POINT}");
println!("--- rustc sees ---\n{}\n", expand(POINT));
println!("--- you write ---\n{MODE}");
println!("--- rustc sees ---\n{}", expand(MODE));
}
$ cargo run
--- you write ---
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

--- rustc sees ---
struct Point {
    x: i32,
    y: i32,
}
#[automatically_derived]
impl ::core::fmt::Debug for Point {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Point", "x",
            &self.x, "y", &&self.y)
    }
}
#[automatically_derived]
impl ::core::clone::Clone for Point {
    #[inline]
    fn clone(&self) -> Point {
        Point {
            x: ::core::clone::Clone::clone(&self.x),
            y: ::core::clone::Clone::clone(&self.y),
        }
    }
}
#[automatically_derived]
impl ::core::marker::StructuralPartialEq for Point { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Point {
    #[inline]
    fn eq(&self, other: &Point) -> bool {
        self.x == other.x && self.y == other.y
    }
}

--- you write ---
#[derive(Default)]
enum Mode {
    #[default]
    Fast,
    Slow,
}

--- rustc sees ---
enum Mode {

    #[default]
    Fast,
    Slow,
}
#[automatically_derived]
impl ::core::default::Default for Mode {
    #[inline]
    fn default() -> Mode { Self::Fast }
}

Each impl is plain code: Clone clones each field, PartialEq compares each field, Debug calls a helper with the name and fields. #[automatically_derived] marks the block as generated; the Reference says it “has no direct effect”. #[default] survives expansion: it only marks the variant for Default.

The built-in derives add a bound for every type parameter, whether the fields need it or not (Reference: impl<T: Clone> Clone for Foo<T>). PhantomData<T>, a field that stores no data, is Clone for any T (docs), yet the derive demands T: Clone:

Rustsrc/main.rs✗ won't compile
use std::marker::PhantomData;
#[derive(Clone)]
struct Handle<T> {
id: u32,
_kind: PhantomData<T>,
}
struct Printer; // no Clone
fn main() {
let h: Handle<Printer> = Handle {
id: 1,
_kind: PhantomData,
};
let again = h.clone();
println!("{}", again.id);
}
$ cargo buildWhat is E0599?
error[E0599]: the method `clone` exists for struct `Handle<Printer>`, but its trait bounds were not satisfied
  --> src/main.rs:16:19
   |
 4 | struct Handle<T> {
   | ---------------- method `clone` not found for this struct because it doesn't satisfy `Handle<Printer>: Clone`
...
 9 | struct Printer; // no Clone
   | -------------- doesn't satisfy `Printer: Clone`
...
16 |     let again = h.clone();
   |                   ^^^^^ method cannot be called on `Handle<Printer>` due to unsatisfied trait bounds
   |
note: trait bound `Printer: Clone` was not satisfied
  --> src/main.rs:4:15
   |
 3 | #[derive(Clone)]
   |          ----- in this derive macro expansion
 4 | struct Handle<T> {
   |               ^ type parameter would need to implement `Clone`
   = help: consider manually implementing the trait to avoid undesired bounds
help: consider annotating `Printer` with `#[derive(Clone)]`
   |
 9 + #[derive(Clone)]
10 | struct Printer; // no Clone

The note says “consider manually implementing the trait to avoid undesired bounds”; Common traits covers when.

Attribute macros

An attribute macro receives the item it sits on and returns what replaces it. #[test] is built into the compiler but works like one: it keeps your function, adds a constant that describes it, and adds a main that runs the list. This is what cargo test builds (it passes --test to the compiler):

Rustsrc/main.rsRun ↗
use std::process::Command;
const SOURCE: &str = "#[test]
fn adds() {
assert_eq!(1 + 1, 2);
}
";
// A harness: `--test` is the flag `cargo test` passes to rustc; RUSTC_BOOTSTRAP=1 as above.
fn expand(test_mode: bool) -> String {
let dir = std::env::temp_dir();
std::fs::write(dir.join("test-demo.rs"), SOURCE).unwrap();
let mut command = Command::new("rustc");
command.args([
"--edition",
"2024",
"--crate-type",
"lib",
"-Zunpretty=expanded",
]);
if test_mode {
command.arg("--test");
}
let out = command
.arg("test-demo.rs")
.current_dir(&dir)
.env("RUSTC_BOOTSTRAP", "1")
.output()
.unwrap();
std::fs::remove_file(dir.join("test-demo.rs")).unwrap();
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn main() {
let text = expand(true);
// From the generated constant to your function, then the generated `main`.
let start = text.find("extern crate test;").unwrap();
let body = text.find("fn adds()").unwrap();
let main_at = text.find("#[rustc_main]").unwrap();
println!("--- with --test ---");
println!("{}", text[start..body].trim_end());
println!("fn adds() {{ ... }} // your function, with assert_eq! expanded");
println!("{}", text[main_at..].trim_end());
println!("--- without --test ---");
println!(
"expansion mentions `adds`: {}",
expand(false).contains("adds")
);
}
$ cargo run
--- with --test ---
extern crate test;
#[rustc_test_marker = "adds"]
#[doc(hidden)]
pub const adds: test::TestDescAndFn =
    test::TestDescAndFn {
        desc: test::TestDesc {
            name: test::StaticTestName("adds"),
            ignore: false,
            ignore_message: ::core::option::Option::None,
            source_file: "test-demo.rs",
            start_line: 2usize,
            start_col: 4usize,
            end_line: 2usize,
            end_col: 8usize,
            compile_fail: false,
            no_run: false,
            should_panic: test::ShouldPanic::No,
            test_type: test::TestType::Unknown,
        },
        testfn: test::StaticTestFn(#[coverage(off)] ||
                test::assert_test_result(adds())),
    };
fn adds() { ... }   // your function, with assert_eq! expanded
#[rustc_main]
#[coverage(off)]
#[doc(hidden)]
pub fn main() -> () {
    extern crate test;
    test::test_main_static(&[&adds])
}
--- without --test ---
expansion mentions `adds`: false

Without --test the expansion does not mention adds: test functions are not part of a normal build (Reference).

Attribute Written by What it does
#[tokio::main] the tokio crate “helps set up a Runtime” for async fn main (docs; Async with Tokio uses it)
#[serde(rename = "x")] serde’s derive a helper attribute that configures the generated code (serde)

Procedural macro crates

A procedural macro is a Rust function that takes tokens in and returns tokens. The Reference says it “must be defined in the root of a crate with the crate type of proc-macro” and “may not be used from the crate where” it is defined; with Cargo the crate sets proc-macro = true under [lib] (Reference). So it lives in a different crate from its users.

The lesson runner compiles one file, so this harness writes a two-crate workspace to a temporary folder and builds it with Cargo; the macros are in the string SHOUT_LIB. It needs no dependencies, because proc_macro ships with the compiler. One macro of each procedural kind; the attribute prints what it receives during compilation:

Rustsrc/main.rsRun ↗
use std::fs;
use std::path::Path;
use std::process::Command;
// A harness: it writes a two-crate workspace to a temporary folder and builds it with Cargo.
// No dependencies: `proc_macro` comes with the compiler.
const WORKSPACE: &str = "[workspace]\nmembers = [\"shout\", \"app\"]\nresolver = \"3\"\n";
// The crate that holds the macros. `proc-macro = true` is what makes it one.
const SHOUT_TOML: &str = "[package]\nname = \"shout\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[lib]\nproc-macro = true\n";
const SHOUT_LIB: &str = r#"use proc_macro::TokenStream;
// make_answer!(): ignore the input and write a function.
#[proc_macro]
pub fn make_answer(_input: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
// #[derive(TypeName)]: read the name after `struct`, write an impl block.
#[proc_macro_derive(TypeName)]
pub fn derive_type_name(input: TokenStream) -> TokenStream {
let text = input.to_string();
let name = text.split_whitespace().nth(1).unwrap().trim_end_matches(';');
format!("impl {name} {{ fn type_name() -> &'static str {{ \"{name}\" }} }}")
.parse()
.unwrap()
}
// #[show_streams(...)]: print what arrives, at compile time, and return the item unchanged.
#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
println!("attr: \"{attr}\"");
println!("item: \"{item}\"");
item
}
"#;
// The crate that uses them.
const APP_TOML: &str = "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nshout = { path = \"../shout\" }\n";
const APP_MAIN: &str = r#"use shout::{TypeName, make_answer, show_streams};
make_answer!();
#[derive(TypeName)]
struct Pet;
#[show_streams(loud, times = 2)]
fn greet() -> &'static str {
"hi"
}
fn main() {
println!("Pet::type_name() = {}", Pet::type_name());
println!("greet() = {}", greet());
println!("answer() = {}", answer());
}
"#;
fn main() {
let root = std::env::temp_dir().join("proc-macro-demo");
let _ = fs::remove_dir_all(&root);
for (file, text) in [
("Cargo.toml", WORKSPACE),
("shout/Cargo.toml", SHOUT_TOML),
("shout/src/lib.rs", SHOUT_LIB),
("app/Cargo.toml", APP_TOML),
("app/src/main.rs", APP_MAIN),
] {
let path = root.join(file);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, text).unwrap();
}
let build = Command::new("cargo")
.args(["build", "--quiet", "--offline", "--package", "app"])
.env("CARGO_TARGET_DIR", root.join("target"))
.current_dir(&root)
.output()
.unwrap();
println!("--- cargo build: success = {} ---", build.status.success());
print!("{}", String::from_utf8_lossy(&build.stdout));
print!("{}", String::from_utf8_lossy(&build.stderr));
println!("--- target/debug/app ---");
let run = Command::new(Path::new(&root).join("target/debug/app"))
.output()
.unwrap();
print!("{}", String::from_utf8_lossy(&run.stdout));
let _ = fs::remove_dir_all(&root);
}
$ cargo run
--- cargo build: success = true ---
attr: "loud, times = 2"
item: "fn greet() -> &'static str { "hi" }"
--- target/debug/app ---
Pet::type_name() = Pet
greet() = hi
answer() = 42

The two attr/item lines came from compile time: the attribute received loud, times = 2 and the whole function as tokens, and returned it unchanged. The function-like macro ignored its input and wrote answer. The derive read the name after struct and wrote an impl. Project structure covers workspaces. The Book’s examples use syn to parse the tokens and quote to write them.

Two Reference facts matter when you read someone else’s code. Procedural macros “are unhygienic”: they act as if their output was written inline. And they have “the same security concerns that Cargo’s build scripts have”: a dependency with one runs code on your machine when you build. A panic in one becomes a compiler error.

When to write one

Writing one is rare. In order of preference:

  1. A function, the easiest to read, test and debug.
  2. Generics and traits (Generics, Traits) for code that must work for many types.
  3. A macro_rules! macro, for one of the five powers above, such as one implementation repeated for a list of types.
  4. A procedural macro, when it must read a struct’s fields or parse its own syntax. It costs a separate crate.

What you write today, in Rust

Practice

Try it

The first exercise reads macros; the others finish small ones. Replace each todo!() and run until the checks print all checks passed.

Which rule fires, and what does it become?

Say which rule of rule! each of four calls matches, then write by hand what twice!(next()) expands to. The checks compare your answers with the compiler’s; one call is not what its shape suggests.

Starterexercises/read-rules.rs
use std::cell::Cell;
// `rule!` answers with the number of the rule that matched. Read the patterns top to bottom.
macro_rules! rule {
($a:expr) => {
1
};
($a:expr, $b:expr) => {
2
};
($a:ident = $b:expr) => {
3
};
($($t:tt)*) => {
4
};
}
// `twice!(e)` pastes its argument in twice.
macro_rules! twice {
($e:expr) => {
$e + $e
};
}
// Part 1: which rule does each call match? Answer from the patterns, in this order:
// `2 + 3`, `1, 2`, `x = 5`, `a, b, c`.
fn which_rules() -> [u32; 4] {
todo!("read the patterns: the first rule that fits the whole call wins")
}
// Part 2: write out by hand what `twice!(next())` becomes.
fn twice_by_hand(next: &dyn Fn() -> i32) -> i32 {
todo!("the macro pastes the argument as written, so what is called, and how often?")
}
fn main() {
let actual = [rule!(2 + 3), rule!(1, 2), rule!(x = 5), rule!(a, b, c)];
let answer = which_rules();
for (i, call) in ["2 + 3", "1, 2", "x = 5", "a, b, c"].iter().enumerate() {
assert_eq!(
answer[i], actual[i],
"wrong rule for the call `rule!({call})`"
);
}
let calls = Cell::new(0);
let next = || {
calls.set(calls.get() + 1);
calls.get()
};
let by_macro = twice!(next());
let macro_calls = calls.get();
calls.set(0);
assert_eq!(
twice_by_hand(&next),
by_macro,
"the value differs from the macro's"
);
assert_eq!(
calls.get(),
macro_calls,
"the macro calls `next` more than once"
);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/read-rules.solution.rsRun ↗
use std::cell::Cell;
// `rule!` answers with the number of the rule that matched. Read the patterns top to bottom.
macro_rules! rule {
($a:expr) => {
1
};
($a:expr, $b:expr) => {
2
};
($a:ident = $b:expr) => {
3
};
($($t:tt)*) => {
4
};
}
// `twice!(e)` pastes its argument in twice.
macro_rules! twice {
($e:expr) => {
$e + $e
};
}
// Part 1: which rule does each call match? Answer from the patterns, in this order:
// `2 + 3`, `1, 2`, `x = 5`, `a, b, c`.
fn which_rules() -> [u32; 4] {
[1, 2, 1, 4]
}
// Part 2: write out by hand what `twice!(next())` becomes.
fn twice_by_hand(next: &dyn Fn() -> i32) -> i32 {
next() + next()
}
fn main() {
let actual = [rule!(2 + 3), rule!(1, 2), rule!(x = 5), rule!(a, b, c)];
let answer = which_rules();
for (i, call) in ["2 + 3", "1, 2", "x = 5", "a, b, c"].iter().enumerate() {
assert_eq!(
answer[i], actual[i],
"wrong rule for the call `rule!({call})`"
);
}
let calls = Cell::new(0);
let next = || {
calls.set(calls.get() + 1);
calls.get()
};
let by_macro = twice!(next());
let macro_calls = calls.get();
calls.set(0);
assert_eq!(
twice_by_hand(&next),
by_macro,
"the value differs from the macro's"
);
assert_eq!(
calls.get(),
macro_calls,
"the macro calls `next` more than once"
);
println!("all checks passed");
}
$ cargo run
all checks passed

Wrap a type, with its name

Finish the Display that newtype!(Meters, f64) writes so it prints Meters(2.5). Two more types are checked, so the name must come from the call.

Starterexercises/newtype.rs
use std::fmt;
// newtype!(Meters, f64) defines `struct Meters(f64)` with `new`, `get`, and a Display that
// prints `Meters(2.5)`: the type's own name, then the value in parentheses.
macro_rules! newtype {
($name:ident, $inner:ty) => {
struct $name($inner);
impl $name {
fn new(value: $inner) -> Self {
$name(value)
}
fn get(&self) -> &$inner {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
todo!("write the name, then the value in parentheses")
}
}
};
}
newtype!(Meters, f64);
newtype!(UserId, u32);
newtype!(Label, String);
fn main() {
let m = Meters::new(2.5);
assert_eq!(*m.get(), 2.5);
assert_eq!(m.to_string(), "Meters(2.5)");
let id = UserId::new(7);
assert_eq!(*id.get(), 7);
assert_eq!(format!("{id}"), "UserId(7)");
let label = Label::new(String::from("hi"));
assert_eq!(label.get(), "hi");
assert_eq!(label.to_string(), "Label(hi)");
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/newtype.solution.rsRun ↗
use std::fmt;
// newtype!(Meters, f64) defines `struct Meters(f64)` with `new`, `get`, and a Display that
// prints `Meters(2.5)`: the type's own name, then the value in parentheses.
macro_rules! newtype {
($name:ident, $inner:ty) => {
struct $name($inner);
impl $name {
fn new(value: $inner) -> Self {
$name(value)
}
fn get(&self) -> &$inner {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}({})", stringify!($name), self.0)
}
}
};
}
newtype!(Meters, f64);
newtype!(UserId, u32);
newtype!(Label, String);
fn main() {
let m = Meters::new(2.5);
assert_eq!(*m.get(), 2.5);
assert_eq!(m.to_string(), "Meters(2.5)");
let id = UserId::new(7);
assert_eq!(*id.get(), 7);
assert_eq!(format!("{id}"), "UserId(7)");
let label = Label::new(String::from("hi"));
assert_eq!(label.get(), "hi");
assert_eq!(label.to_string(), "Label(hi)");
println!("all checks passed");
}
$ cargo run
all checks passed

The largest of any number of values

Finish the second rule of max_of!: compare the first argument with the maximum of the rest. The checks count evaluations.

Starterexercises/max-of.rs
use std::cell::Cell;
// max_of!(a, b, c, ...) is the largest of one or more values, written as a macro because a
// function cannot take any number of arguments.
macro_rules! max_of {
($x:expr $(,)?) => {
$x
};
($head:expr, $($tail:expr),+ $(,)?) => {{
let first = $head;
// The starter hands back `first` so the types work out; the rest is yours.
if false {
first
} else {
todo!("compare first with the maximum of the rest, evaluating each argument once")
}
}};
}
fn main() {
assert_eq!(max_of!(7), 7);
assert_eq!(max_of!(3, 9, 4), 9);
assert_eq!(max_of!(-5, -2), -2);
assert_eq!(max_of!(1, 2 + 3 * 4, 5), 14);
assert_eq!(max_of!("pear", "apple", "fig"), "pear");
assert_eq!(max_of!(1.5, 0.5,), 1.5); // a trailing comma is allowed
// Each argument is evaluated exactly once, left to right.
let calls = Cell::new(0);
let next = || {
calls.set(calls.get() + 1);
calls.get()
};
let largest = max_of!(next(), next(), next());
assert_eq!(calls.get(), 3, "each argument must be evaluated once");
assert_eq!(largest, 3);
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/max-of.solution.rsRun ↗
use std::cell::Cell;
// max_of!(a, b, c, ...) is the largest of one or more values, written as a macro because a
// function cannot take any number of arguments.
macro_rules! max_of {
($x:expr $(,)?) => {
$x
};
($head:expr, $($tail:expr),+ $(,)?) => {{
let first = $head;
let rest = max_of!($($tail),+);
if first > rest { first } else { rest }
}};
}
fn main() {
assert_eq!(max_of!(7), 7);
assert_eq!(max_of!(3, 9, 4), 9);
assert_eq!(max_of!(-5, -2), -2);
assert_eq!(max_of!(1, 2 + 3 * 4, 5), 14);
assert_eq!(max_of!("pear", "apple", "fig"), "pear");
assert_eq!(max_of!(1.5, 0.5,), 1.5); // a trailing comma is allowed
// Each argument is evaluated exactly once, left to right.
let calls = Cell::new(0);
let next = || {
calls.set(calls.get() + 1);
calls.get()
};
let largest = max_of!(next(), next(), next());
assert_eq!(calls.get(), 3, "each argument must be evaluated once");
assert_eq!(largest, 3);
println!("all checks passed");
}
$ cargo run
all checks passed

Return from the caller

Write ensure!(cond): when false, the calling function returns an Err naming the condition as written. One check has a side effect in the condition.

Starterexercises/ensure.rs
use std::cell::Cell;
// ensure!(condition) returns early from the surrounding function with an Err that names the
// condition, as written, when it is false. A function cannot do that: only a macro can
// return from its caller, and only a macro can see the condition as text.
macro_rules! ensure {
($cond:expr) => {
todo!("if the condition is false, return Err(String) that says which condition failed")
};
}
fn check(age: i32, name: &str) -> Result<(), String> {
ensure!(age >= 0);
ensure!(!name.is_empty());
ensure!(age + 1 < 150);
Ok(())
}
fn side_effect(calls: &Cell<i32>) -> Result<(), String> {
ensure!({
calls.set(calls.get() + 1);
true
});
Ok(())
}
fn main() {
assert_eq!(check(30, "Ada"), Ok(()));
assert_eq!(check(-1, "Ada"), Err("ensure failed: age >= 0".to_string()));
assert_eq!(
check(30, ""),
Err("ensure failed: !name.is_empty()".to_string())
);
assert_eq!(
check(200, "Ada"),
Err("ensure failed: age + 1 < 150".to_string())
);
// The first failing check ends the function: the later ones never run.
assert_eq!(check(-1, ""), Err("ensure failed: age >= 0".to_string()));
let calls = Cell::new(0);
assert_eq!(side_effect(&calls), Ok(()));
assert_eq!(calls.get(), 1, "the condition must be evaluated once");
println!("all checks passed");
}

Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.

Show a solution
One solutionexercises/ensure.solution.rsRun ↗
use std::cell::Cell;
// ensure!(condition) returns early from the surrounding function with an Err that names the
// condition, as written, when it is false. A function cannot do that: only a macro can
// return from its caller, and only a macro can see the condition as text.
macro_rules! ensure {
($cond:expr) => {
if !$cond {
return Err(format!("ensure failed: {}", stringify!($cond)));
}
};
}
fn check(age: i32, name: &str) -> Result<(), String> {
ensure!(age >= 0);
ensure!(!name.is_empty());
ensure!(age + 1 < 150);
Ok(())
}
fn side_effect(calls: &Cell<i32>) -> Result<(), String> {
ensure!({
calls.set(calls.get() + 1);
true
});
Ok(())
}
fn main() {
assert_eq!(check(30, "Ada"), Ok(()));
assert_eq!(check(-1, "Ada"), Err("ensure failed: age >= 0".to_string()));
assert_eq!(
check(30, ""),
Err("ensure failed: !name.is_empty()".to_string())
);
assert_eq!(
check(200, "Ada"),
Err("ensure failed: age + 1 < 150".to_string())
);
// The first failing check ends the function: the later ones never run.
assert_eq!(check(-1, ""), Err("ensure failed: age >= 0".to_string()));
let calls = Cell::new(0);
assert_eq!(side_effect(&calls), Ok(()));
assert_eq!(calls.get(), 1, "the condition must be evaluated once");
println!("all checks passed");
}
$ cargo run
all checks passed

Read more