Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"src/generics",
"src/iterators",
"src/lifetimes",
"src/macros/proc-macros/exercise",
"src/memory-management",
"src/methods-and-traits",
"src/modules",
Expand Down
1,183 changes: 592 additions & 591 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,72 @@ SPDX-License-Identifier: CC-BY-4.0

---

# Macros: Morning

- [Welcome](macros/welcome.md)
- [What Is A Macro](macros/intro.md)
- [What Are Macros For](macros/intro/why.md)
- [How Do Macros Work](macros/intro/how.md)
- [C Preprocessor vs. Rust Macros](macros/intro/cpp-vs-rust-macros.md)
- [A C Preprocessor Macro Example](macros/intro/cpp-example.md)
- [A Rust Macro Example](macros/intro/rust-example.md)
- [Macros In Rust](macros/design.md)
- [Ways To Define Macros](macros/defining.md)
- [Where Macros Can Apply](macros/kinds.md)
- [Function-Like Macros](macros/kinds/function-like.md)
- [Derive Macros](macros/kinds/derive.md)
- [Attribute Macros](macros/kinds/attr.md)
- [Macros, Tokens, and Syntax](macros/tokens.md)
- [Brackets and Token Trees](macros/brackets.md)
- [Token Streams](macros/streams.md)
- [Macros By Example](macros/macro_rules/welcome.md)
- [A Macros-By-Example Example](macros/macro_rules/example.md)
- [Defining Macros By Example](macros/macro_rules/defining.md)
- [Pattern Matching](macros/macro_rules/pattern-matching.md)
- [Exercise: Saving Some Typing](macros/macro_rules/pattern-matching/exercise.md)
- [Solution](macros/macro_rules/pattern-matching/solution.md)
- [Fragment Specifiers](macros/macro_rules/fragment-specifiers.md)
- [Exercise: Pair Macro](macros/macro_rules/fragment-specifiers/exercise.md)
- [Solution](macros/macro_rules/fragment-specifiers/solution.md)
- [Repetition](macros/macro_rules/repetition.md)
- [Exercise: Generalized Operations](macros/macro_rules/repetition/exercise.md)
- [Solution](macros/macro_rules/repetition/solution.md)

# Macros: Afternoon

- [Declarative Macro Techniques](macros/macro_rules/techniques.md)
- [Optional Parameters](macros/macro_rules/techniques/optional-params.md)
- [Named Positional Parameters](macros/macro_rules/techniques/named-params.md)
- [Internal Rules](macros/macro_rules/techniques/internal-rules.md)
- [Token Munchers](macros/macro_rules/techniques/token-munchers.md)
- [Exercise: Turtle Graphics](macros/macro_rules/techniques/token-munchers/exercise.md)
- [Solution](macros/macro_rules/techniques/token-munchers/solution.md)
- [Push-Down Accumulators](macros/macro_rules/techniques/accumulators.md)
- [Exercise: Turtle Graphics II](macros/macro_rules/techniques/accumulators/exercise.md)
- [Solution](macros/macro_rules/techniques/accumulators/solution.md)
- [TT Bundling](macros/macro_rules/techniques/tt-bundling.md)
- [Hygiene](macros/hygiene.md)
- [What Is Macro Hygiene](macros/hygiene/what-is-hygiene.md)
- [Hygiene In Rust Macros](macros/hygiene/rust-macro-hygiene.md)
- [Procedural Macros](macros/proc-macros/welcome.md)
- [Procedural Macro Basics](macros/proc-macros/basics.md)
- [Function-Like Procedural Macros](macros/proc-macros/function-like.md)
- [Derive Macros](macros/proc-macros/derive.md)
- [Attribute Macros](macros/proc-macros/attr.md)
- [Handling Errors](macros/proc-macros/errors.md)
- [Procedural Macros In The Wild](macros/proc-macros/in-the-wild.md)
- [Writing Procedural Macros](macros/proc-macros/writing.md)
- [Dependencies](macros/proc-macros/writing/deps.md)
- [The `proc_macro` Crate](macros/proc-macros/writing/deps/proc_macro.md)
- [The `proc_macro2` Crate](macros/proc-macros/writing/deps/proc_macro2.md)
- [The `syn` and `quote` Crates](macros/proc-macros/writing/deps/syn-quote.md)
- [The `syn` AST](macros/proc-macros/writing/deps/syn-ast.md)
- [The `quote!` macro](macros/proc-macros/writing/deps/quote-macro.md)
- [Exercise: `Display` Derive](macros/proc-macros/exercise.md)
- [Solution](macros/proc-macros/solution.md)

---

# Android

- [Welcome](android.md)
Expand Down
38 changes: 38 additions & 0 deletions src/macros/brackets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# Brackets and Token Trees

A **token tree** can represent either a single token, or a **group** of tokens
enclosed by matching delimiters (brackets):

- **Single tokens (leaf nodes):** `foo`, `+`, `,`, `123`.
- **Grouped tokens (internal nodes):** Enclosed by parentheses `()`, braces
`{}`, or square brackets `[]`.

For example, the token stream: `foo + (bar * baz)`

Is parsed into **3 separate token trees**:

1. `foo` (a single token)
2. `+` (a single token)
3. `(bar * baz)` (a token group containing three child token trees: `bar`, `*`,
and `baz`)

Because grouping happens _during_ lexical analysis, **unbalanced groups are
strictly disallowed**. You cannot pass unbalanced parentheses or braces into or
out of a macro!

<details>

- Note that this means you cannot use a macro to generate half a block, like
`let x = {` and close it with another macro or tokens outside the macro. The
entire block must be passed or returned as a single, well-formed token group.

</details>
31 changes: 31 additions & 0 deletions src/macros/defining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# Ways To Define Macros

There are two separate ways of implementing macros in Rust. Each has distinct
advantages and trade-offs:

| Feature | Declarative Macros | Procedural Macros |
| ------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **Usage** | Function-like macros only | All three kinds (Derive, Attr, Function-like) |
| **Location** | Implemented within your normal crate | Must be defined in a separate `proc-macro` crate |
| **Pros** | - Low boilerplate<br>- No extra compilation step<br>- Easy to write and reuse | - Extremely powerful and expressive<br>- Written in standard Rust<br>- Full programmatic control |
| **Cons** | - Bespoke pattern-matching syntax<br>- Cannot inspect arbitrary token structures | - Can slow down build time<br>- Substantial boilerplate required |
| **Hygiene** | Partially/mixed hygienic by default | Configurable / custom hygiene |

<details>

- Explain that procedural macros are literally compiled as libraries and
executed _inside_ the compiler while compiling the consuming code. This is why
they require a separate crate.
- Emphasize that you should always prefer declarative macros for simple code
generation due to their much smaller impact on build times.

</details>
42 changes: 42 additions & 0 deletions src/macros/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# Macros In Rust

Rust macros provide structured code generation. There are **three kinds** of
macros, and **two ways** they are implemented.

### Kinds of Macros

- **Derive Macros:** Added to type definitions (structs, enums, unions) to
auto-implement traits (e.g., `#[derive(Default)]`).
- **Function-Like Macros:** Invoked with an exclamation mark in
item/statement/expr context (e.g., `println!("Hello!")`, `vec![1, 2, 3]`, or
`include_bytes!("manifest.bin")`).
- **Attribute Macros:** Attached as custom attributes to any item, like
functions or modules (e.g., `#[tokio::main]`).

### Implementation Forms

- **Declarative Macros (AKA "Macros By Example"):** Part of the language itself,
based on pattern matching; these can only be used to define function-like
macros.
- **Procedural Macros:** Rust functions running as compiler plugins that
transform token streams. These can perform arbitrary operations at compile
time, including I/O if desired.

<details>

- Highlight that students have already used function-like macros (like
`println!`, `vec!`, `format!`) and derive macros (like
`#[derive(Clone, Debug)]`).
- Explain that attribute macros are very popular in libraries like Tokio or
Axum, often transforming functions similar to Python decorators.

</details>
38 changes: 38 additions & 0 deletions src/macros/hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# Hygiene

A subtle aspect of macro systems is their degree of **hygiene**, or independence
from the lexical environment of their expansion.

Macro hygiene enables macros to avoid accidentally being influenced by or
polluting the scope of the code surrounding their call sites.

In this section, we will cover:

- What macro hygiene is and why it is important.
- How unhygienic macros can result in bugs or impede understanding code.
- The extent to which Rust macros are hygienic and the how partial hygiene in
Rust macros works.

<details>

- Explain that in many preprocessor-based languages (such as C), macros are
completely unhygienic, operating solely on raw tokens and potentially
interacting with the lexical environment differently at each expansion. This
means that the ability to reason about macros agnostic of the context in which
they will expand is extremely limited. This can lead to bugs when names used
in macros coincide with names used at their call sites.
- The notion of hygiene may be familiar to students who know LISP, as it
famously exhibits fully hygienic macros.
- This slide serves as a transition into the detailed discussion of macro
hygiene.

</details>
85 changes: 85 additions & 0 deletions src/macros/hygiene/rust-macro-hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# Hygiene In Rust Macros

Declarative macros in Rust are partially hygieninic.

- They are hygienic with respect to: local variables, parameters, loop labels,
and the special `$crate` variable.
- They are **not** hygienic with respect to: items, types, methods, and traits.

## Rationale

Frequently, rust macros are used as shorthand to refer to existing types and
traits, e.g. when defining `impl`s. In this situation, hygienic macros would
always need to accept all relevant items as arguments, imposing a floor beneath
which we could not decrease lexical boilerplate.

On the other hand, hygiene helps us write reliable code, so it is desirable for
any internal operations that a macro may want to perform. Luckily for us, Rust
does provide a solution for hygienic references to items.

### The `$crate` variable

Item and crate paths are unhygienic, so item paths within a macro definition
could will refer to a different item than intended if their leading module or
crate name is defined differently at the call site than the macro author
expected.

In general, declarative macros themselves cannot carry along crate dependencies
in a hygienic way. However, there is a way out: to unambiguously refer to the
macro's **defining crate** only, the `$crate` metavariable may be used.

`$crate` expands to the root path of the crate that defined the macro. This can
be used to refer to local helper items without fear of interference, regardless
of the macro call site. These local helpers may call or re-exports items from
the standard library or other dependencies.

```rust,compile_fail
// Macro-defining crate `my_macros`
pub fn my_macro_helper(s: &str) {
std::io::print(s)
}

macro_rules! print_something {
($args:tt) => {
// Safe from shadowing of the standard library or any other crate,
// because items from this crate accessed with $crate are hygienic!
$crate::my_macro_helper(stringify!($args))
};
}

// Macro-consuming crate that alters meaning of the `std` crate name
#![no_std]

// libcore exports many similar APIs to libstd, but not `io::print`
extern crate core as std;

fn main() {
my_macros::print_something!()
}
```

<details>

- Carefully delineate dependencies in the example: the program as a whole
depends on libstd, but in the top-level crate it is not a direct dependency,
and libcore is imported with its name instead. libcore does not export
`io::print`, so a straightforward reference to `std::io::print` in the macro
would expand to a non-existing path. But because the macro crate does depend
on libstd, and the macro only accesses its own local helper through the
`$crate` metavariable, it is able to reliably refer to the stdlib (or another
crate) indirectly.
- Explain that to enforce hygiene on local variables, the compiler keeps track
of "syntax contexts." A local variable defined inside the macro has a
different syntax context than a variable of the same name defined outside,
which prevents collisions.

</details>
56 changes: 56 additions & 0 deletions src/macros/hygiene/what-is-hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
minutes: 5
---

<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: CC-BY-4.0
-->

# What Is Macro Hygiene

A macro system is **unhygienic** if a macro can:

1. Implicitly access identifiers in the surrounding callsite scope.
2. Define a new local identifier that bleeds out and is implicitly accessible by
the surrounding callsite.

### Example 1: Implicitly Accessing Callsite State (Unhygienic)

```rust,ignore
macro_rules! use_local {
() => {
// Unhygienic: attempts to implicitly read `local` from callsite
println!("{}", local);
};
}

fn main() {
let local = "Hello, Macros!".to_string();
use_local!(); // In an unhygienic system, this would compile!
}
```

### Example 2: Leaking Local Variables (Unhygienic)

```rust,ignore
macro_rules! make_local {
() => {
// Unhygienic: attempts to leak `local` to callsite
let local = "Hello, Macros!".to_string();
};
}

fn main() {
make_local!();
println!("{}", local); // In an unhygienic system, this would compile!
}
```

In Rust, **neither of these examples compile**. Both produce the error:
`error[E0425]: cannot find value 'local' in this scope`.

Rust's macro system treats variables hygienically, protecting from silent
namespace pollution.

However, declarative macros in Rust are not fully hygienic!
Loading