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)
Comment on lines +297 to +303

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [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)
- [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)

I prefer to avoid deep nesting of slides like this. While teaching I often look at the table of contents to remind myself how many slides I have left, which helps with time management. Nesting slides like this somewhat interferes with my ability to do that quickly.

If you think it's worth de-nesting this a bit, then I think the morning slides could be structured a bit more flatly as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nesting is semantically helpful to understand the structure of the information, but I agree and have a similar workflow when teaching--I often want to see how many slides I need to cover and which are coming up next. What if we made it possible (or the default) to expand all children?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a conservative compromise for now is to just unindent the individual syn and quote slides.

- [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.
Comment on lines +20 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could benefit from an example showing how macros that interact with with items get more verbose when you have to explicitly pass in everything. Maybe something that implements a trait for a bunch of types, and would have to have the trait and all of the method names passed in explicitly?


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section on $crate should be pulled out into its own slide.


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
could will refer to a different item than intended if their leading module or
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.
Comment on lines +31 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use a dedicated example showing how a path in a macro gets interpreted relative to where the macro is expanded, maybe also splitting this into its own slide.


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!()
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is very confusing to me:

  • It references std::io::print, which doesn't appear to be a thing.
  • Uses #[no_std], which is something students may not understand but would be awkward to explain here.
  • It tries to illustrate that using #[no_std] in a downstream crate would change what std means in the macro, except the macro only references my_macro_helper. The speaker note somewhat explains what's going on here, but I think the code example needs to illustrate that better.

I think the better example would be to have two crates that define the same function, and show that using $crate allows the macro to always reference the one from the crate where the macro is defined.

Alternative example code

Crate A, which defines a function and a macro that references the function:

pub fn print() {
    println!("Hello from crate A");
}

macro_rules! print_something {
    () => {
        // With $crate this will always reference `print` in this
        // same crate, without it the macro will call whatever
        // `print` is in scope where the macro is invoked.
        $crate::print()
    };
}

Crate B, which defines its own print and uses the macro from crate A:

use crate_a::print_something;

pub fn print() {
    println!("Hello from crate B");
}

fn main() {
    print_something!();
}

We'd then show that the program prints "Hello from crate A", and if $crate is removed then it'll print "Hello from crate B". Not sure if we can actually demonstrate that in the slides though, since it involves multiple crates interacting :/

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My motivation here was to produce a somewhat realistic situation where this would occur--the most likely way macros might rely on ambiently-available names is via the standard library, but some consumers may violate that expectation. For some reason I thought there was a non-macro std::io::print in libstd, but the closest thing actually exposed is probably std::io::stdout().write_all(...).

But given that #![no_std] is not something folks are likely to be familiar with when they want to learn how to use macros, you're probably right that an example with two hypothetical crates is better.

And yeah, I don't think we can provide a working interactive example here because what's really happening is a cross-crate interaction. But I'll change the example to something closer to what you suggest.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having thought about this more, I think it might be best to split this slide in half. The first can demonstrate how the lack of hygiene for paths can make a macro refer to different items when invoked from different locations, and show how we might use absolute paths to try to avoid that situation. This can be done with multiple modules in a single crate. The second can show that even absolute paths do not solve this problem completely in the presence of multiple crates, and motivate/explain the $crate keyword. We kind of try to do this already, but the existing "what is hygiene" slide is really just introducing the concept without demonstrating how the semantics of Rust item paths interact with it.


<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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two examples from this slide could probably be broken into two separate slides. Maybe three: One for the "What Is Macro Hiegiene" section at the top, and then two more slides for the two examples.

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!
Comment on lines +50 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines could be pulled into speaker notes to make the slide a bit more concise.

Loading