---
title: "24.x Chapter 24 summary and quiz"
description: "Review of declarative macro_rules! macros, when to use a macro versus a function or generic, and the three kinds of procedural macro."
url: "https://learnrust.net/chapter-24/chapter-24-summary-and-quiz/"
last_updated: "2026-06-13"
---

# 24.x Chapter 24 summary and quiz

A short chapter on the part of Rust that writes Rust. Review, then a quiz.

## Quick review

A **macro** is code that writes code at compile time: it takes your source and expands it into more source ([24.1](https://learnrust.net/chapter-24/declarative-macros/)). That's the `!` you've typed since lesson 1.1, "this is expanded, not called." Macros do what functions can't: variable argument counts (`println!`), construction syntax (`vec!`), generating repetitive code.

**Declarative macros** are written with `macro_rules!`: a set of rules, each a *matcher* (left of `=>`) and an *expansion* (right). Capture input with metavariables like `$x:expr` (also `ident`, `ty`, `literal`, `tt`), and match "any number of" with repetition `$( ... ),*`, which `vec!` uses to emit one `push` per element. Macros are also **hygienic**: their internal identifiers can't collide with the caller's.

**When to use one** ([24.2](https://learnrust.net/chapter-24/when-to-write-a-macro/)): ask "could this be a function?" first (usually yes, prefer it). If the variation is across *types*, use a generic (chapter 15). Reach for a macro only for what neither can do, variable arguments, generating repetitive code across types, new syntax, operating on code itself, because macros cost readability and have rougher errors.

**Procedural macros** are compile-time *functions* that manipulate code, more powerful than `macro_rules!`, in three kinds ([24.3](https://learnrust.net/chapter-24/procedural-macros-overview/)): **derive** (`#[derive(Debug)]`, `#[derive(Serialize)]`, generate code from a type's structure), **attribute** (`#[tokio::main]`, transform a tagged item), and **function-like** (`sqlx::query!`, procedurally-powered `name!(...)`). The crates you use most (serde, Tokio) are essentially procedural macros; you'll *use* them constantly and *write* them rarely.

## Quiz time

**Question #1**

What does the `!` in `println!` actually signify, finally?

<details class="solution">
<summary>Show solution</summary>

It marks a *macro* invocation rather than a function call. The macro is expanded at compile time into real source code (the actual formatting-and-writing logic), which the compiler then compiles. That's why `println!` can take a variable number of arguments, something no function signature allows. The "supercharged function" from lesson 1.1 was a macro: code that writes code, expanded before compilation.

</details>

**Question #2**

For each, say whether you'd use a function, a generic, or a macro: (a) compute the average of a `Vec<f64>`; (b) find the max of a slice of any orderable type; (c) build a collection from a variable-length list of elements written inline.

<details class="solution">
<summary>Show solution</summary>

(a) **Function**, fixed arguments, fixed type, a plain computation. (b) **Generic**, the variation is across *types*, so a generic function with a `PartialOrd` bound (like `largest<T: PartialOrd>`) keeps full type checking. (c) **Macro**, a variable number of inline elements (like `vec![...]`) can't be a function signature, and the construction syntax is exactly what macros provide. The rule: fixed → function; varies by type → generic; varies by argument count or needs new syntax → macro.

</details>

**Question #3**

You write `#[derive(Debug, Clone)]` on a struct. What kind of macro is `derive`, and what does it do here?

<details class="solution">
<summary>Show solution</summary>

`derive` is a *procedural macro* (specifically a derive macro). At compile time it inspects the struct's structure (its fields and their types) and generates trait implementations: `Debug` produces a `fmt` that prints the fields, `Clone` produces a `clone` that clones each field. You wrote two words and the macros generated two full `impl` blocks of boilerplate, code tailored to your type, which is what makes procedural macros more powerful than pattern-matching `macro_rules!`.

</details>

**Question #4**

Why are Rust's macros being "hygienic" a meaningful safety improvement over C's preprocessor macros?

<details class="solution">
<summary>Show solution</summary>

Hygiene means identifiers a macro uses internally are kept separate from identifiers in the calling code, so they can't accidentally collide. In C, a macro that uses a variable named `temp` can silently clash with the caller's `temp` and corrupt it, a classic, hard-to-find bug. Rust's compiler keeps the two namespaces distinct automatically, so this whole class of name-collision bug can't happen, making macros far safer to use.

</details>

You now understand the whole macro system, from the `!` in your first program to the `#[derive]`s that power the ecosystem, and the judgment of when code-generation is the right tool. Chapter 25 enters Rust's most dangerous territory: `unsafe`. For 24 chapters the compiler has guaranteed memory safety. Now you'll learn the small set of operations where it *can't*, why they exist, and how Rust contains them, so that "unsafe" means "a contract you uphold," not "safety off."

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
