---
title: 'Traits, Generics and Error Handling'
source: 'https://academia.sh/en/courses/rust-abstraction'
language: en
updated: '2026-09-15T04:58:41+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'Builds a behavior contract by defining a trait'
    - 'Makes the cost of an abstraction visible by measuring how many times a body ran'
    - 'Makes error paths visible in the type with Option and Result'
    - 'Writes declarative transformations with iterators and closures'
---

# Traits, Generics and Error Handling

In Rust the cost of an abstraction shows not in how much you write in the source but in how much runs at run time: the course measures every construct with one question — how many times did this body you wrote run? Traits, generics, error paths, closures and iterator chains all pass through the same frame.

## Traits and Generics

A behavior contract defined independent of type, generic code stating its requirement with a bound, a contract carrying a type within itself, choosing whether a call is resolved at compile time or at run time, unwritten bodies produced through deriving, and operators gaining behavior through a contract.

### Trait Definition and Implementation

Source: https://academia.sh/en/courses/rust-abstraction/trait-definition-and-implementation

A trait establishes a behavior contract independent of type and can carry a default method body; when a sixteen-body measurement kernel runs, this default body is seen to run four times for one type and zero times for a type that overrides it — the question asked throughout the course is this: how many times did this body you wrote in the source run?

The Ownership and the Memory Model course measured where a value's life ends: at the end of
its scope, where it was moved, in an explicit call, or together with its container. While a
value stays in hand for the length of its life, **what is done with it** is a separate
question, and this course looks there. Separating a behavior from a type — introducing the
same function under the same name to more than one type — turns into a decision that also
determines **how many times** that behavior's body runs. One question is asked throughout the
course: how many times did this body you wrote in the source run?

There are three answers to this question, and these three names do not change throughout the
course.

#### Three Classes

| Class | Definition |
|---|---|
| every time | Runs unavoidably, per call or per element |
| only when needed | Runs only when the result is requested, as much as needed |
| never | Written in the source, but did not run even once in this run |

This trio should not be confused with the two questions already asked earlier. The
Object-Oriented Java course measured *which type made the decision* — the declared type or
the runtime type. The Interfaces, Errors and Generics course (Go) measured *where the binding
was established* — at declaration, at the call site, or inside the value. This course asks
neither; it asks **counting**. By the time a trait implementation is written, the binding is
already established — what's measured in this lesson is how many times that body was
triggered at run time, after the binding was established.

A trait is a behavior contract: it declares one or more method signatures and lets those
methods be called under a type-independent name. The function definition in the Programming
Fundamentals course was bound to a single type; a trait generalizes this — the same method
name can correspond to a separate body for every type that implements it. This looks close to
the interface concept, but it is not the same thing: an interface was defined in the Go course
on the axis of *where* the binding is established (implicit implementation, without requiring
a declaration); a trait, in Rust, is a contract that a type declares **explicitly** with the
`impl Name for Type` syntax, and it can also carry a default body. The word trait will be used
untranslated throughout the course.

#### Required and Optional Methods

A trait declaration lists a series of method signatures inside the body of `trait Name { ... }`.
Each signature can take one of two forms: only a header (`fn name(&self) -> Type;`, ending in
a semicolon, without a body) or a header plus a body (`fn name(&self) -> Type { ... }`). The
first form is **required**: every type that implements the trait must write this method in
its own `impl` block, otherwise the implementation is considered incomplete and the source
does not compile. The second form is **optional**: an implementing type may never write this
method — if it does not, the trait's own body takes over; if it does, its own body runs and the
trait's is never seen.

The `Label` trait used throughout this lesson shows all three at once. `ledger` and `kind` are
declared without a body — every implementation has to write these two itself, because the
trait itself cannot assume anything about a type's ledger or name. `name`, on the other hand,
is declared with a default body: `Plain` never writes this method and inherits the trait's
body, while `Custom` writes its own body and overrides the default. Required and optional
methods can coexist inside the same trait declaration; this is similar to a contract saying
that some of its clauses must differ for every signer (address, name) while others can start
with a shared default and be changed only when needed.

The `impl Label for Plain` syntax itself splits into two parts as well: `Label` says which
contract is being implemented; `Plain` says who implements it. The same trait can be
implemented repeatedly for different types with separate `impl` blocks — `times.rs` has two
separate `impl Label for ...` blocks for `Plain` and `Custom`, both carrying the same trait
name but implementing independent bodies.

#### The Measurement Kernel

The kernel from which the rest of the course will take slices is `times.rs`. It classifies
sixteen separate bodies in a single run and records each one to an observation ledger. The
ledger reuses the mechanism built in the previous course's shared-ownership lesson:
`Rc<RefCell<BTreeMap<String, u32>>>` — a counter with more than one owner, carrying interior
mutability, and sorted.

```rust
// times.rs — how many times did a body run: every time, only when needed, or never.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

// --- trait: default body and the type that overrides it ---

trait Label {
    fn ledger(&self) -> &Ledger;
    fn kind(&self) -> &'static str;
    fn name(&self) -> String {
        let key = format!("default-body-{}", self.kind());
        record(self.ledger(), &key);
        String::from("default")
    }
}

struct Plain {
    ledger: Ledger,
}
struct Custom {
    ledger: Ledger,
}

impl Label for Plain {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "plain"
    }
}

impl Label for Custom {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "custom"
    }
    fn name(&self) -> String {
        record(self.ledger(), "overridden-body");
        String::from("custom")
    }
}

// --- generics vs. trait object: the same job, two call forms ---

fn static_dispatch<T: Label>(x: &T) -> String {
    record(x.ledger(), "static-dispatch");
    x.name()
}

fn dynamic_dispatch(x: &dyn Label) -> String {
    record(x.ledger(), "dynamic-dispatch");
    x.name()
}

// --- operator overloading ---

#[derive(Clone)]
struct Length {
    value: i32,
    ledger: Ledger,
}

impl Add for Length {
    type Output = Length;
    fn add(self, other: Length) -> Length {
        record(&self.ledger, "operator-body");
        Length {
            value: self.value + other.value,
            ledger: Rc::clone(&self.ledger),
        }
    }
}

// --- the error path ---

#[derive(Debug)]
struct NumberError;

impl From<std::num::ParseIntError> for NumberError {
    fn from(_: std::num::ParseIntError) -> NumberError {
        NumberError
    }
}

fn parse_doubled(ledger: &Ledger, text: &str, label: &str) -> Result<i32, NumberError> {
    let n: i32 = text.parse()?;
    record(ledger, label);
    Ok(n * 2)
}

fn eager_default(ledger: &Ledger) -> i32 {
    record(ledger, "eager-default");
    -1
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let mut classified: Vec<(&str, &str)> = Vec::new();

    // 1-2. default body: runs for the type that does not override it, never for the one that does
    let plain = Plain {
        ledger: Rc::clone(&ledger),
    };
    let custom = Custom {
        ledger: Rc::clone(&ledger),
    };
    let _ = plain.name();
    let _ = custom.name();
    let _ = custom.name();

    // 3-4. the same job in two call forms
    let _ = static_dispatch(&plain);
    let _ = static_dispatch(&plain);
    let _ = dynamic_dispatch(&plain as &dyn Label);
    let _ = dynamic_dispatch(&custom as &dyn Label);

    // 5. operator body
    let a = Length {
        value: 3,
        ledger: Rc::clone(&ledger),
    };
    let b = Length {
        value: 4,
        ledger: Rc::clone(&ledger),
    };
    let sum = a.clone() + b;
    let sum = sum + a;
    let _ = sum.value;

    // 6. eager default: runs even when a value IS present
    let full: Option<i32> = Some(7);
    let _ = full.unwrap_or(eager_default(&ledger));

    // 7-8. lazy default: never when a value is present, once when it is absent
    let empty: Option<i32> = None;
    let _ = full.unwrap_or_else(|| {
        record(&ledger, "lazy-default-unneeded");
        -1
    });
    let _ = empty.unwrap_or_else(|| {
        record(&ledger, "lazy-default-needed");
        -1
    });

    // 9-10. after the question mark operator
    let _ = parse_doubled(&ledger, "21", "post-question-succeeded");
    let _ = parse_doubled(&ledger, "twenty-one", "post-question-failed");

    // 11. error conversion
    if parse_doubled(&ledger, "twenty-one", "ignored").is_err() {
        record(&ledger, "error-conversion");
    }

    // 12. unconsumed chain
    let _lazy = vec![1, 2, 3, 4].into_iter().map(|x| {
        record(&ledger, "unconsumed-chain");
        x * 2
    });

    // 13. consumed chain: on every element
    let sum: i32 = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "consumed-chain");
            x * 2
        })
        .sum();
    let _ = sum;

    // 14. short-circuiting chain: only as much as needed
    let found = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "short-circuiting-chain");
            x * 2
        })
        .find(|x| *x > 2);
    let _ = found;

    // 15. uncalled closure
    let _closure = || record(&ledger, "uncalled-closure");

    // --- report ---
    let bodies = [
        ("default-body-plain", "every time"),
        ("overridden-body", "every time"),
        ("static-dispatch", "every time"),
        ("dynamic-dispatch", "every time"),
        ("operator-body", "every time"),
        ("eager-default", "every time"),
        ("consumed-chain", "every time"),
        ("error-conversion", "every time"),
        ("lazy-default-needed", "only when needed"),
        ("post-question-succeeded", "only when needed"),
        ("short-circuiting-chain", "only when needed"),
        ("default-body-custom", "never"),
        ("lazy-default-unneeded", "never"),
        ("post-question-failed", "never"),
        ("unconsumed-chain", "never"),
        ("uncalled-closure", "never"),
    ];

    println!("{:<28}{:<22}{}", "body", "class", "how many times");
    for (name, class) in bodies {
        let n = times(&ledger, name);
        println!("{:<28}{:<22}{}", name, class, n);
        classified.push((name, class));
    }

    println!();
    for s in ["every time", "only when needed", "never"] {
        let count = classified.iter().filter(|(_, c)| *c == s).count();
        println!("{:<22}{}", s, count);
    }
    println!("total bodies: {}", classified.len());
}
```

```
body                        class                 how many times
default-body-plain          every time            4
overridden-body             every time            3
static-dispatch             every time            2
dynamic-dispatch            every time            2
operator-body               every time            2
eager-default               every time            1
consumed-chain              every time            4
error-conversion            every time            1
lazy-default-needed         only when needed      1
post-question-succeeded     only when needed      1
short-circuiting-chain      only when needed      2
default-body-custom         never                 0
lazy-default-unneeded       never                 0
post-question-failed        never                 0
unconsumed-chain            never                 0
uncalled-closure            never                 0

every time            8
only when needed      3
never                 5
total bodies: 16
```

**TG1.** This run is deterministic: it was verified across five separate runs that the
source's same sixteen lines are produced in the same order with the same class distribution.
**8 of the sixteen bodies ran every time, 3 ran only when needed, 5 never ran.** This number
does not change through the rest of the course; each lesson only takes its own slice. The
report uses a sorted map (`BTreeMap`) — because a hash map's iteration order could change from
run to run, the count here relies on a structure that guarantees ordering even for this.

#### A Default Body Gives Two Different Results for Two Types

The `Label` trait declares three methods: `ledger` and `kind` carry no body at all — every
implementation has to write these itself. The third method, `name`, carries a **default
body**: since `impl Label for Plain` never touches this body, the trait's own body runs when
`name` is called on `Plain`. `Custom`, on the other hand, **overrides** `name` with its own
body — the default body in the trait never takes over for `Custom`.

The two rows in the table show this pair directly. `default-body-plain` ran four times:
`plain.name()` was called directly once, `static_dispatch(&plain)` was called twice and each
call triggered `x.name()` inside itself, and `dynamic_dispatch(&plain ...)` was called once
and it triggered `x.name()` the same way — four calls in total, all four landing on the
trait's default body. `overridden-body` ran three times: `custom.name()` twice, directly, and
`dynamic_dispatch(&custom ...)` once — all three ran `Custom`'s own body, never touching the
trait's default. Both belong to the **every time** class: they run unavoidably wherever
they are called, the only difference being which body runs.

**TG2.** The Interfaces, Errors and Generics course measured **where the binding was
established** for an implementation — in Go, a type implicitly implemented an interface
without any declaration. In Rust this binding is established **explicitly** with the
`impl Label for Plain` line; this course notes that difference once and moves on. What's
actually measured here is how many times that implementation's body is triggered at run time
**after** the binding is established — the difference of 4 vs. 3 between `default-body-plain`
and `overridden-body` comes from the number of calls, not from the binding itself.

`eager-default`, `operator-body`, and `error-conversion` on the error path fall into the same
class too — they run every time, because each one in the kernel runs exactly as many times as
it is called. The course's later lessons will each grow one of these rows in turn: Trait
Bounds will grow whether `static-dispatch` depends on a bound, Associated Types will grow the
`Output` field in `operator-body`'s signature, Object Safety and Dynamic Dispatch will put
`static-dispatch` and `dynamic-dispatch` side by side.

#### The Measurement's Limit

The `default-body-custom` row is zero. This does not mean the default body **does not exist** —
it sits in the source, inside the `trait Label` declaration, exactly as it did when it ran for
`Plain`. The reason it is zero is that `Custom` writes its own `name` method and never touches
this body. The same source line runs four times for one type while never running for another;
the run count does not measure whether the body exists, it measures which types **use** it.

This distinction will come up again through the rest of the course: none of the five bodies in
the `never` class have been deleted from the source — all five were not triggered even
once in this run. A count of zero does not mean "this code is unnecessary"; it means "this path
was not chosen in this run."

The default body itself was also written to make this visible: its key string is built by
placing the value returned by the `self.kind()` call into a pattern, not a fixed string. The
trait's own body never knows the name of the type running it — `self.kind()` produces a
string, and the body borrows it and appends it to the key. This lets a single default body be
counted in a separate row for every type that inherits it; even if `Plain` and `Custom` shared
the same default body (in this example they do not, because `Custom` wrote its own), they could
still be counted separately under two separate keys. This small design decision also explains
why the kernel can be sliced in the rest of the lessons: every body carries its own key, so
taking one slice does not affect the remaining keys.

#### Summary

- A trait establishes a type-independent behavior contract and can carry a default body that
  takes over when the implementing type does not write one.
- The measurement kernel classifies sixteen bodies: 8 ran every time, 3 only when needed, 5
  never — this number does not change through the course.
- The same default body runs four times (every time) for one type while running zero times
  (never) for a type that overrides it — the body in the source is the same, the run count
  differs.
- trait and interface are separate concepts: interface was defined in Go on the axis of where
  the binding is established, trait here is a contract established with an explicit
  declaration that can carry a default body.
- A run count of zero does not mean the body is missing from the source; it means that path was
  never taken for that type.

#### Next Step

The body of a type implementing a trait could be measured, but to use a body inside a generic
function, that function's type parameter needs a **bound**. The next lesson asks whether this
bound touches the run count at all — the same body is written and run in both a bounded and an
unbounded form.

### Trait Bounds

Source: https://academia.sh/en/courses/rust-abstraction/trait-bounds

Putting a trait bound on a generic function's type parameter adds no step to the source at all — the same body written and run in both a bounded and an unbounded form gives back the exact same number in both; the bound's cost is written to the compilation, not to the run ledger, and it cannot be measured by this number.

The previous lesson showed that a trait can carry a default body and that which type this body
runs for can be counted in the run. But the `<T: Label>` syntax of a function like
`static_dispatch` has not been explained yet — this is a **trait bound** placed on a generic
function's type parameter, and it is this lesson's subject. The question stays the same: how
many times did this body you wrote in the source run? But this time what's measured is not the
body — it is the **condition** placed around the body.

#### What the Bound Says

A generic function written as `fn name<T>(x: &T)` can accept any type; but if the body wants
to call a method on `x`, the compiler needs to know that method exists. A trait bound provides
exactly this: the syntax `fn name<T: Label>(x: &T)` says "whatever `T` is, it must implement
`Label`." Without the bound, a call like `x.ledger()` does not compile, because the compiler
does not know which methods exist for `T`; once the bound is added, the compiler reads this
information from the trait declaration.

An unbounded generic function means the compiler knows nothing about `T`. When `fn name<T>(x:
&T)` is written, the compiler only allows operations common to every type on `x` (moving it,
taking a reference) — if a call specific to `Label`, like `x.ledger()`, is written, the
compiler rejects it, because all it knows is that `T` *is a type*, not that it implements
`Label`. Unlike the type ledger in the previous course, this rejection is not a run-time
behavior here — the source does not compile at all. A trait bound fills exactly this gap: from
the moment `T: Label` is written, the compiler knows that every method `Label` declares can be
called on `x` inside the body, and `x.ledger()` becomes something that compiles. The bound thus
binds both sides at once: the calling side ("only types that implement `Label` can be passed to
this function") and the body side ("this body can safely call `Label`'s methods").

The same bound can be written in two different places. The short form goes right next to the
type parameter: `fn bounded_body<T: Label>(x: &T)`. When the bound gets long (multiple traits,
long type names), the same meaning can also be written with a `where` clause:

```rust
// where-form.rs — the same bound, written apart from the signature.
trait Label {
    fn name(&self) -> String;
}

struct Example;
impl Label for Example {
    fn name(&self) -> String {
        String::from("example")
    }
}

fn short<T: Label>(x: &T) -> String {
    x.name()
}

fn long<T>(x: &T) -> String
where
    T: Label,
{
    x.name()
}

fn main() {
    let e = Example;
    println!("short={} long={}", short(&e), long(&e));
}
```

```
short=example long=example
```

**TG3.** `short` and `long` mean exactly the same thing to the compiler; the `where` clause is
a readability option, not a separate mechanism. When more than one bound is needed (something
like `T: Label + Clone`), the short form makes the line longer, while the `where` form can keep
the signature short by putting each bound on its own line. This lesson uses the two
interchangeably; whichever is chosen has no effect at all on the run count.

Multiple bounds are joined with the `+` sign: `T: Label + Clone` means "`T` must implement both
`Label` and `Clone`." This is a familiar structure from the previous lesson: `Label` alone was
a single contract; here two contracts are bound to the same type parameter at once. `+`
establishes an **intersection** here, not a union: the set of types that can stand in for `T`
is the intersection of the set of types implementing only `Label` and the set of types
implementing only `Clone`. Adding a bound always **narrows** which types can be passed to a
function, never widens it — so adding a new bound to a signature can retroactively cut off
some calls, but it can never make a new call possible.

Bounds also do not change *where* the type parameter is constrained: the only difference between
`<T: Label>` and `where T: Label` is placement, not the strength of the constraint. The same
principle holds when two bounds are written together (`T: Label + Clone`): the compiler, while
checking the body, assumes `T` carries the methods of *both* `Label` *and* `Clone`; if either
bound were missing, a single call belonging to that trait would already make the body fail to
compile.

A signature can carry more than one type parameter, and each parameter can have its own bound:
in `fn combine<T: Label, U: Label>(a: &T, b: &U)`, `T` and `U` look like they share the same
bound, but to the compiler they are two independent parameters — `a` and `b` can be different
concrete types, the only shared requirement is that both implement `Label`. Bounds are placed
per parameter; one parameter's bound does not affect another, even if the same trait name is
repeated in both places.

```rust
// combine.rs — two type parameters, each independently carrying the same trait bound.
trait Label {
    fn name(&self) -> String;
}

struct Plain;
struct Custom;

impl Label for Plain {
    fn name(&self) -> String {
        String::from("plain")
    }
}
impl Label for Custom {
    fn name(&self) -> String {
        String::from("custom")
    }
}

fn combine<T: Label, U: Label>(a: &T, b: &U) -> String {
    format!("{}+{}", a.name(), b.name())
}

fn main() {
    let p = Plain;
    let c = Custom;
    println!("{}", combine(&p, &c));
    println!("{}", combine(&p, &p));
}
```

```
plain+custom
plain+plain
```

In the second call, `T` and `U` both resolve to the same concrete type (`Plain`); in the
first, they resolve to two different concrete types (`Plain` and `Custom`). `combine`'s
signature accepts both calls, because the bound only says "must implement `Label`," not "must
be the same type." If we wanted to force `T` and `U` to resolve to the same concrete type, the
signature would use a single parameter instead (`fn combine<T: Label>(a: &T, b: &T)`); having
two separate parameters exists precisely to **remove** that requirement.

When no trait bound is written, a type parameter is not left completely empty: every `T`, unless
stated otherwise, also carries an implicit **size** bound — the compiler assumes `T` has a
fixed, known size in memory, because a generic body needs to be able to hold a value of type
`T` locally. This implicit bound is a separate layer from explicitly written bounds like
`Label`, and it is invisible for most generic functions; it only needs to be explicitly relaxed
in a small number of advanced writings that work with types of variable size, like slices.
This lesson does not use that relaxation — what matters here is knowing that alongside the bound
that is **visible** (`Label`), there is always an invisible default bound as well.

#### The Bound's Contribution to the Run: Zero Steps

The same job can be set up in two forms to compare the run count: one generic with a
`<T: Label>` bound, the other a direct concrete type (`Plain`), an unbounded function. A third
function uses more than one bound (`Label + Clone`) together.

```rust
// times.rs — the slice of trait Label: `ledger`, the type name, and two implementations.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

pub trait Label {
    fn ledger(&self) -> &Ledger;
    fn kind(&self) -> &'static str;
}

#[derive(Clone)]
pub struct Plain {
    pub ledger: Ledger,
}

impl Label for Plain {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "plain"
    }
}

pub struct Custom {
    pub ledger: Ledger,
}

impl Label for Custom {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "custom"
    }
}
```

```rust
// bound-body.rs — measures that a trait bound adds no step at all to the run.
mod times;
use times::{times, record, Ledger, Label, Custom, Plain};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

// The same source line is generated separately by the compiler for every
// concrete type it is called with — this is a cost the run counter does not see.
fn bounded_body<T: Label>(x: &T) -> u32 {
    let key = format!("bounded-body-{}", x.kind());
    record(x.ledger(), &key);
    times(x.ledger(), &key)
}

// The same job, unbounded: takes a concrete type directly, no type parameter.
fn concrete_body(x: &Plain) -> u32 {
    record(x.ledger(), "concrete-body");
    times(x.ledger(), "concrete-body")
}

// T must provide both Label and Clone — two bounds joined with '+'.
fn clone_and_record<T: Label + Clone>(x: &T) -> T {
    record(x.ledger(), "multi-bound-body");
    x.clone()
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let plain = Plain { ledger: Rc::clone(&ledger) };
    let custom = Custom { ledger: Rc::clone(&ledger) };

    let _ = bounded_body(&plain);
    let _ = bounded_body(&plain);
    let _ = bounded_body(&custom);

    let _ = concrete_body(&plain);
    let _ = concrete_body(&plain);

    let clone = clone_and_record(&plain);
    let _ = clone_and_record(&clone);

    let mut bodies: BTreeMap<&str, u32> = BTreeMap::new();
    bodies.insert("bounded-body-custom", times(&ledger, "bounded-body-custom"));
    bodies.insert("bounded-body-plain", times(&ledger, "bounded-body-plain"));
    bodies.insert("concrete-body", times(&ledger, "concrete-body"));
    bodies.insert("multi-bound-body", times(&ledger, "multi-bound-body"));

    println!("{:<22}{}", "body", "how many times");
    for (name, n) in &bodies {
        println!("{:<22}{}", name, n);
    }
}
```

```
body                  how many times
bounded-body-custom   1
bounded-body-plain    2
concrete-body         2
multi-bound-body      2
```

**TG4.** `bounded-body-plain` ran twice, `concrete-body` also ran twice — the exact same
number. Both do the same job (reading the ledger and counting one row); one through a generic
type parameter, the other directly through a concrete type. The trait bound adds or subtracts
nothing from **how many times** the body is called — this shows that the bound leaves no trace
at all in the run ledger. A bound is a **compile-time** rule: it decides which types can be
passed to `bounded_body`, but once passed, the running code behaves as if the bound never
existed.

The Interfaces, Errors and Generics course measured, for Go's generics, that **the binding was
established at the call site** — a generic function's type constraint restricted which types a
call could be made with, at the call site. This course is not asking that same question; what's
measured here is **what the bound adds to the run** — and the answer is nothing.
`bounded-body-custom` confirms this from a second angle: the same `bounded_body` function can
also be called with `Custom` (once), because `Custom` also implements `Label`; the bound
decides which types are accepted, but the body runs the same way for every accepted type.

`multi-bound-body` falls into the same class too: called twice, ran twice. Here the
`T: Label + Clone` bound combines two separate conditions, but the body itself is still a
single record call plus a single clone operation. As the number of bounds grows (`Label`, then
`Label + Clone`), the line count in the run ledger does not grow; a bound is a condition placed
on the type parameter, not a step added inside the body.

All four rows fall into the **every time** class: none of them is "only when needed" or
"never," because every call unavoidably finds its match — there is no skipped, deferred, or
unconsumed step. This is the same class as `default-body-plain` and `overridden-body` from the
previous lesson: the only difference is which condition the body can be called under, not
whether the body itself runs.

This equality will come up again under a different question later in the course. The Object
Safety and Dynamic Dispatch lesson will compare the same `Label` contract, this time using
`&dyn Label` instead of the `<T: Label>` bound, against a call form. There too the count comes
out equal — but for a different reason than what's seen here: bounded generics resolve the
call at compile time, while the `dyn` form finds the same call at run time from a method table.
This lesson only shows that the bound's **existence** adds nothing to the run; how the two call
forms differ from each other is taken up in a separate lesson.

`concrete_body` not needing a bound at all is also a consequence of the same principle. When a
function is already bound to a single concrete type (`Plain`), the answer to "which types
should be accepted" is already known from the start — only `Plain`. A trait bound exists for
cases where the answer to this question can be **more than one** type; there is no need to even
ask it in a function bound to a single type. So the difference between `concrete_body` and
`bounded_body` is not a shortcoming — it is the answer to two different questions: one never asks
"which type is right for this job," the other answers that question with a bound.

#### The Measurement's Limit

Having no cost in the run does not mean the bound is **free**. When the `bounded_body` function
is called separately for `Plain` and `Custom`, the compiler works by a method that generates a
**separate body for every concrete type**: there is a single `fn bounded_body<T: Label>` line in
the source, but the resulting binary has one body for `Plain` and another for `Custom`. This
generation happens during compilation and never enters the run ledger — the ledger only counts
calls at run time, not how many bodies were generated at compile time. This course's
measurement method **cannot show** this cost; the lesson says so openly and does not try to count
it. `bounded-body-plain` and `bounded-body-custom` running three times in total carries no
information at all about how many separate bodies were generated behind the scenes.

This limit also exposes a general limit of the course's measurement method: the run ledger can
only count lines that are **actually passed at run time**. Nothing that happens at compile
time — how many separate bodies were generated, in what order each type was compiled, how many
candidate types a bound eliminated — can be made visible with this ledger. Throughout the
course, the word "count" always means a **run-time count**; when a question belongs to compile
time, the lesson says so openly and answers it in prose, instead of making up a number. This
distinction matters for setting out from the start what the measurement does and does not cover:
for the rest of the course, the sentence "this body never ran" is always an observation that
belongs to run time, not a claim about whether the source exists.

#### Summary

- A trait bound places the condition "must implement this trait" on a generic function's type
  parameter; the short form (`<T: Label>`) and the `where` clause mean the same thing to the
  compiler.
- Multiple bounds are joined with `+` (`T: Label + Clone`); the body stays a single one, running
  code does not grow as the bound count grows.
- A bounded body (`bounded-body-plain`) and an unbounded body doing the same job
  (`concrete-body`) ran the exact same number of times every time: two each — the bound adds no
  step at all to the run ledger.
- The bound's real cost is not in the run but in the compilation: a separate body is generated
  for every concrete type, but this generation cannot be measured by the run counter, and the
  lesson does not try to.

#### Next Step

The bound tied a trait to the type parameter from the outside. A trait can sometimes want to
carry a type **within** the contract itself — a type the caller never states, that the
implementer decides. The next lesson compares this associated type against writing the same
contract with a type parameter instead.

### Associated Types

Source: https://academia.sh/en/courses/rust-abstraction/associated-types

A trait can carry a type within itself without taking a type parameter — an associated type; when the same contract is written with an associated type and with a generic parameter, the run count comes out exactly the same, the difference is in what the caller has to write in the signature, and an associated type carries a constraint that allows only one implementation per type.

The previous lesson showed how a trait bound constrains a type parameter **from the outside**:
the `T: Label` syntax said which types the caller could pass in. A trait can sometimes do the
opposite too — carry a type **within** the contract itself, a type decided not by the caller
but by the implementer. The kernel that ran in the course's first lesson already had an example
of this: the `Add` trait's implementation carried the line `type Output = Length;` — that line
in the operator-overloading section is exactly this lesson's subject, the **associated type**.
That lesson passed over it without introducing it; this lesson comes back to it.

A trait bound answered the caller's question of "which types can I pass to this function." An
associated type answers a different question: "a type implementing this contract carries which
**other** type alongside itself." A trait can declare not just method signatures but also a
helper type those methods work with — this helper type can differ per implementation, but is
fixed for any single implementation. This means a contract can define not just "what will be
done" but also "which type it will work with."

Neither of the two bodies measured in this lesson leaves the course's three classes — both fall
into just one: **every time**. Neither the associated type nor the generic parameter causes a
body to be deferred or skipped — both only shape the body's **signature**, not whether it runs
when called. This is the same observation as the previous two lessons: the class is determined
not by how the body is named or where it is bound, but by what happens when it is called.

#### What Is an Associated Type

Writing `type Name;` inside a trait declaration means "every type implementing this contract
must also declare a `Name` type within itself." During implementation, this slot is filled by a
`type Name = ConcreteType;` line. The two traits below define the same job with two different
notations:

```rust
// times.rs — slice: the record/times ledger, the associated-type and generic-parameter contracts.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

// Contract carries a type within itself: Product is decided by the implementer.
pub trait Contract {
    type Product;
    fn produce(&self, ledger: &Ledger) -> Self::Product;
}

// The same idea, with a generic parameter: C is a type the caller supplies from outside.
pub trait GenericContract<C> {
    fn produce_generic(&self, ledger: &Ledger) -> C;
}

pub struct Container {
    pub value: i32,
}

impl Contract for Container {
    type Product = i32;
    fn produce(&self, ledger: &Ledger) -> i32 {
        record(ledger, "associated-type-body");
        self.value
    }
}

impl GenericContract<i32> for Container {
    fn produce_generic(&self, ledger: &Ledger) -> i32 {
        record(ledger, "generic-parameter-body");
        self.value
    }
}
```

The `Self::` prefix in the `Self::Product` syntax is not arbitrary: when the trait declaration
is written, it is not yet known which type will implement this contract, so `Product` cannot be
written as a bare name — it has to be written as `Self::Product`, meaning "the implementing
type's own `Product`." Inside the `impl` block this ambiguity disappears — since `Self` now
corresponds to `Container`, the line `type Product = i32;` can write `i32` directly, with no
need for the `Self::` prefix. The same name (`Product`) plays two different roles: a
**placeholder** in the trait declaration, a **decision** inside the implementation.

In `Contract`, `Product` is **part of** the contract: the return type of the `produce` method
is written as `Self::Product`, not a concrete type name. When `Container` implements this
contract, it declares once, at the top of the `impl` block, that `Product` is `i32`; the rest
of the block inherits this decision. In `GenericContract<C>`, the same information is instead
**inside the trait's name**: when `impl GenericContract<i32> for Container` is written, `C`
resolves to `i32` there, but because this information is part of the trait name, it can be
repeated more than once, with different `C` values — the constraint side of this difference is
looked at below.

#### Same Count, Different Syntax

Calling both notations the same number of times makes the run count directly comparable.

```rust
// associated-type.rs — the same job in two notations: associated type and generic parameter.
mod times;
use times::{times, Ledger, Container, Contract, GenericContract};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let container = Container { value: 9 };

    // The caller never writes Product in the associated-type form - the compiler already knows it.
    let _ = container.produce(&ledger);
    let _ = container.produce(&ledger);
    let _ = container.produce(&ledger);

    // The same number of calls, the same body, with the generic parameter too.
    let _ = GenericContract::<i32>::produce_generic(&container, &ledger);
    let _ = GenericContract::<i32>::produce_generic(&container, &ledger);
    let _ = GenericContract::<i32>::produce_generic(&container, &ledger);

    let mut bodies: BTreeMap<&str, u32> = BTreeMap::new();
    bodies.insert("associated-type-body", times(&ledger, "associated-type-body"));
    bodies.insert("generic-parameter-body", times(&ledger, "generic-parameter-body"));

    println!("{:<24}{}", "body", "how many times");
    for (name, n) in &bodies {
        println!("{:<24}{}", name, n);
    }
}
```

```
body                    how many times
associated-type-body    3
generic-parameter-body  3
```

**TG5.** Both ran three times — two rows in the **every time** class, the exact same number.
The difference does not show up in the run ledger, it shows up in the call line itself:
`container.produce(&ledger)` never says what `Product` is, because the compiler already knows
it from `Container`'s `Contract` implementation. `GenericContract::<i32>::produce_generic(&container,
&ledger)`, on the other hand, carries `i32` explicitly — because `Container` could have more
than one implementation of `GenericContract` (shown below), stating which one is meant becomes
the caller's job.

The Interfaces, Errors and Generics course wrote Go's generics from the **binding** axis; what's
measured here is not the question that course asked, it is the two notations' **body count** —
and the count comes out exactly equal. This shows that an associated type and a generic
parameter carry no different cost at run time; the difference between them is entirely in the
source's **readability** and in the information the caller has to **write**.

#### An Associated Type Is Per Type

The constraint applies not to the trait **as a whole**, but separately to each
**implementation**. `Container`'s `Product` is fixed to `i32` once, but another type can
implement the same `Contract` with its own `Product` — what's being constrained is not "this
trait must use one `Product` everywhere," it is "this type must have chosen one `Product` for
this trait."

```rust
// two-types.rs — an associated type is fixed per implementation, not per trait.
mod times;
use times::{times, Ledger, Container, Contract, GenericContract};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

struct Labeled {
    name: String,
}

impl Contract for Labeled {
    type Product = String;
    fn produce(&self, ledger: &Ledger) -> String {
        times::record(ledger, "labeled-body");
        self.name.clone()
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let container = Container { value: 5 };
    let labeled = Labeled { name: String::from("title") };

    let numeric: i32 = container.produce(&ledger);
    let textual: String = labeled.produce(&ledger);
    let _ = GenericContract::<i32>::produce_generic(&container, &ledger);

    println!("numeric={} textual={}", numeric, textual);
    println!("associated-type-body={}", times(&ledger, "associated-type-body"));
    println!("labeled-body={}", times(&ledger, "labeled-body"));
}
```

```
numeric=5 textual=title
associated-type-body=1
labeled-body=1
```

**TG8.** `Container` implements the same `Contract` with `Product = i32`, `Labeled` with
`Product = String`, and both sit side by side in the same source — this shows that the trait
itself is not bound to one type, only *that type's implementation* is. The constraint still
holds: neither `Container` nor `Labeled` can implement `Contract` a second time with a
different `Product`. The example also calls `GenericContract<i32>` on `Container` once more —
to show that two separate contracts (`Contract` and `GenericContract`) can coexist on the same
type in the same source, without excluding each other.

#### Using It Inside Generic Code

The difference looks small while calling on a single concrete type (`Container`): both add
about three words, a difference of barely a few characters. The difference grows once `Contract`
or `GenericContract` is used **in the bound of another generic function**.

```rust
// generic-use.rs — using Contract in the bound of another generic function.
mod times;
use times::{times, record, Ledger, Container, Contract, GenericContract};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

// No need to write the Product type separately - T::Product is resolved by the compiler.
fn apply<T: Contract>(x: &T, ledger: &Ledger) -> T::Product {
    record(ledger, "generic-use-body");
    x.produce(ledger)
}

// The same idea with a generic parameter: C has to be ADDED to the signature SEPARATELY.
fn apply_generic<T: GenericContract<C>, C>(x: &T, ledger: &Ledger) -> C {
    record(ledger, "generic-use-generic-body");
    x.produce_generic(ledger)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let container = Container { value: 9 };

    let a = apply(&container, &ledger);
    let b = apply_generic(&container, &ledger);

    println!("apply={} apply_generic={}", a, b);
    println!("generic-use-body={}", times(&ledger, "generic-use-body"));
    println!("generic-use-generic-body={}", times(&ledger, "generic-use-generic-body"));
}
```

```
apply=9 apply_generic=9
generic-use-body=1
generic-use-generic-body=1
```

**TG7.** `apply`'s return type is `T::Product` — whatever `T` resolves to, its `Product` is
inherited automatically; there is no separate slot for `Product` in the signature, because none
is needed. `apply_generic`, after writing `T: GenericContract<C>` in its bound, is also forced
to add `C` to the function's **own** type parameter list — `C` cannot be inferred automatically
from `T`, because `T` could in principle carry more than one implementation of
`GenericContract`, and the compiler could not know which one is meant. This shows that the
difference at the call site from the previous section turns into a **signature** difference
when writing generic code: `apply`'s signature carries a single type parameter while
`apply_generic`'s carries two, even though the second function's only job is the same as the
first's.

The Trait Bounds lesson's `combine<T: Label, U: Label>` example showed how two independent type
parameters can stand side by side; `apply_generic<T: GenericContract<C>, C>` uses the same
notation, but here `C` is not an independent choice — it is a repetition of information `T`
already carries. The two functions fit syntactically into the same pattern — more than one
parameter inside `<...>` — but while in `combine` each parameter really answers a different
question, in `apply_generic`, `C` only asks the caller to rewrite information `T` already
carries. An associated type removes this repetition.

#### The Measurement's Limit

An associated type adds a **constraint** to the contract: a type can implement `Contract` only
**once**, because `Product` is bound to a single value for that type. Once `Product = i32` is
declared for `Container`, a second `impl Contract for Container` block — a second one that
tries to make `Product` be `String` — is rejected by the compiler; this rejection is not put in a
code block, because source that does not compile is never shown as a code block in this
repository. The constraint itself is described in prose; the accepted counterpart is already
run above by `associated-type.rs` — there is a single `Contract` implementation for `Container`,
and the source compiles as it stands.

`GenericContract<C>` does not carry this same constraint: because `C` is part of the trait name,
`GenericContract<i32>` and `GenericContract<String>` are **two separate traits** to the
compiler, and `Container` can implement both at once.

```rust
// constraint.rs — a generic parameter allows more than one implementation for the same type;
// an associated type does not (one type = one Product binding).
trait GenericContract<C> {
    fn produce_generic(&self) -> C;
}

struct Container {
    value: i32,
}

impl GenericContract<i32> for Container {
    fn produce_generic(&self) -> i32 {
        self.value
    }
}

impl GenericContract<String> for Container {
    fn produce_generic(&self) -> String {
        self.value.to_string()
    }
}

fn main() {
    let container = Container { value: 9 };
    let numeric: i32 = GenericContract::<i32>::produce_generic(&container);
    let textual: String = GenericContract::<String>::produce_generic(&container);
    println!("numeric={} textual={}", numeric, textual);
}
```

```
numeric=9 textual=9
```

**TG6.** `Container` implements both `GenericContract<i32>` and `GenericContract<String>` at
the same time — both are accepted, both compile. This shows that the constraint an associated
type offers is not a shortcoming but a deliberate design choice: if `Contract` had been used
instead, `Container`'s `Product` would be fixed to a single value, and the caller would never
have to ask "which `Product`?" Using `GenericContract` buys this flexibility, but its cost shows
up at the call site: `container.produce_generic(&ledger)` alone is not enough, since `Container`
carries two implementations — which `C` is meant (`::<i32>` or `::<String>`) has to be written
explicitly. With an associated type, this statement is never needed, because there is only ever
one option.

This constraint should be read not as a shortcoming but as a **design tool**. If a contract
expects, by the nature of a type, to work with a single output type (say, "the element type it
holds" for a container, or "the result type it produces" for a converter), an associated type
turns this uniqueness into a syntax rule: the implementer cannot accidentally implement the same
type with two different output types, because the compiler does not allow it. A generic
parameter does not force this uniqueness — where a type genuinely can work with more than one
output form (as seen in this lesson, where a `Container` can have both a numeric and a textual
counterpart), it is the right tool. The reason both notations exist is that "one type = one
output" and "one type = more than one output" ask for different contracts.

#### Summary

- An associated type is a type slot a trait carries within itself with a `type Name;`
  declaration, filled by the implementer; a generic parameter carries the same information
  inside the trait's name.
- Both notations fall into the every-time class and give exactly the same run count (three and
  three); the difference is not at run time, it is in the information the caller has to write in
  the signature.
- An associated type carries a constraint: a type can implement a trait with its associated
  type only once.
- A generic parameter does not carry this constraint: the same type can implement the same
  trait more than once with different parameter values — the cost is that the caller has to
  state explicitly which implementation is meant.

#### Next Step

These two lessons showed that the run count does not change whether the same body is written
with a bound or with an associated type — both are resolved at compile time. The next lesson
turns to the question of **when** the call is resolved: is the same contract used with a call
resolved at compile time, or one resolved at run time, and does this pair change the run count?

### Object Safety and Dynamic Dispatch

Source: https://academia.sh/en/courses/rust-abstraction/object-safety-and-dynamic-dispatch

A call written with a trait bound resolves at compile time; a call written through a trait object (dyn) resolves at run time — in the measurement kernel both run twice; the difference is not in the count, it is in how the call is found, and this is only possible for traits that satisfy the object safety condition.

The Associated Types lesson showed that both ways of using a contract (associated type,
generic parameter) are resolved **at compile time**. There is a third way to use a trait, and
it is resolved not at compile time but **at run time**: the trait object. The kernel that ran in
the course's first lesson already had this distinction — `static_dispatch<T: Label>` and
`dynamic_dispatch(x: &dyn Label)` did the same job in two different call forms. This lesson
opens up that difference: whenever the call is resolved, how many times does the body run?

#### Two Call Forms

The `static_dispatch<T: Label>(x: &T)` syntax is familiar from the previous two lessons: `T` is
a type parameter, bound to `Label`. The compiler generates this function separately for every
concrete type it is called with — as the Trait Bounds lesson showed. The
`dynamic_dispatch(x: &dyn Label)` syntax is different: `x`'s type is no longer a type
parameter, it is `dyn Label` — "a reference to any value implementing `Label`, whose type will
be determined at run time." The `dyn` keyword is how you tell the compiler "I do not yet know
the concrete type behind this reference, but I know I can call `Label`'s methods on it."

These two forms exist because they answer different questions. Static dispatch generates a
separate body for every concrete type called — this generation happens at compile time, as the
Trait Bounds lesson showed, and never enters the run ledger. But this generation carries a
cost too: when a function writes `<T: Label>`, every place that calls that function has to know,
at compile time, which concrete `T` is used. If a list, a field, or a return value wants to say
"could be either `Plain` or `Custom`," static dispatch cannot express this — a type parameter has
to resolve to a single concrete type. Dynamic dispatch fills exactly this gap: `dyn Label`, by
saying "it does not matter which concrete type it is, as long as it implements `Label`," makes it
possible to hold different types in the same place. This lesson shows that the two do not
**replace** each other, they **complete** each other.

These two notations also differ at the binary level. When `static_dispatch` is called, the
compiler reads which method will run directly from the source and embeds it into the call.
When `dynamic_dispatch` is called, `x` has to carry along a **method table** — a trait object
is actually made of two parts: a reference to the value itself, and a table holding the
addresses of methods belonging to that value's concrete type. Which body runs is **found** from
this table at call time; the single line written in the source (`x.name()`) can open onto two
different bodies depending on the type behind `x`.

`Box<dyn Label>` is built from two words, and both were already introduced in a separate
course. `Box` is a container that establishes single ownership on the heap: it places the value
it holds on the heap and keeps a single owner. `dyn Label` does not name **which** concrete type
sits inside this container, it only says which contract it satisfies. Combined, the resulting
`Box<dyn Label>` means "a value sitting on the heap, implementing `Label`, but whose exact
concrete type has been forgotten." This forgetting is deliberate: `Box<Plain>` and `Box<Custom>`
are two different types to the compiler and cannot go in the same array; `Box<dyn Label>` can go
in the same array because it is the common denominator of both.

#### The Run Count Does Not Change

```rust
// times.rs — slice: Label, Plain, Custom for static and dynamic dispatch.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

pub trait Label {
    fn ledger(&self) -> &Ledger;
    fn name(&self) -> String {
        String::from("default")
    }
}

pub struct Plain {
    pub ledger: Ledger,
}

impl Label for Plain {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
}

pub struct Custom {
    pub ledger: Ledger,
}

impl Label for Custom {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn name(&self) -> String {
        String::from("custom")
    }
}

pub fn static_dispatch<T: Label>(x: &T) -> String {
    record(x.ledger(), "static-dispatch");
    x.name()
}

pub fn dynamic_dispatch(x: &dyn Label) -> String {
    record(x.ledger(), "dynamic-dispatch");
    x.name()
}
```

```rust
// dispatch.rs — compares the run count of static and dynamic dispatch.
mod times;
use times::{times, record, Ledger, Label, Custom, Plain, dynamic_dispatch, static_dispatch};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let plain = Plain { ledger: Rc::clone(&ledger) };
    let custom = Custom { ledger: Rc::clone(&ledger) };

    let _ = static_dispatch(&plain);
    let _ = static_dispatch(&plain);

    let _ = dynamic_dispatch(&plain as &dyn Label);
    let _ = dynamic_dispatch(&custom as &dyn Label);

    // The same trait object can hold different concrete types in one list - static
    // dispatch could not do this, because a Vec<T> is locked to one concrete T.
    let list: Vec<Box<dyn Label>> = vec![
        Box::new(Plain { ledger: Rc::clone(&ledger) }),
        Box::new(Custom { ledger: Rc::clone(&ledger) }),
        Box::new(Custom { ledger: Rc::clone(&ledger) }),
    ];
    // The loop itself knows how many elements there are, but not which body will
    // run on each call - that is resolved inside name() based on the element's real type.
    let names: Vec<String> = list
        .iter()
        .map(|e| {
            record(e.ledger(), "list-body");
            e.name()
        })
        .collect();

    let mut bodies: BTreeMap<&str, u32> = BTreeMap::new();
    bodies.insert("static-dispatch", times(&ledger, "static-dispatch"));
    bodies.insert("dynamic-dispatch", times(&ledger, "dynamic-dispatch"));
    bodies.insert("list-body", times(&ledger, "list-body"));

    println!("{:<18}{}", "body", "how many times");
    for (name, n) in &bodies {
        println!("{:<18}{}", name, n);
    }
    println!("list-names={:?}", names);
}
```

```
body              how many times
dynamic-dispatch  2
list-body         3
static-dispatch   2
list-names=["default", "custom", "custom"]
```

**TG9.** `static-dispatch` ran twice, `dynamic-dispatch` also twice — the exact same number.
The difference is not in the count: both call forms trigger the body unavoidably, both fall into
the **every time** class. The Object-Oriented Java course measured *which type made the
decision* — the declared type or the runtime type. What's measured here is not that question;
what's measured is whether the two dispatch forms **change the body count** — they do not. No
matter how many times `Plain`'s and `Custom`'s `name` methods are called, whether the call form
is static or dynamic never increases or decreases this number.

The `list-body` row shows the practical consequence of this: `Vec<Box<dyn Label>>` can hold
different concrete types (`Plain` and two separate `Custom`s) **in a single collection** — the
`Box` built in the Ownership and the Memory Model course's Heap-Allocated Single Ownership
lesson, here holds a trait object inside it. `static_dispatch<T: Label>` could not do this: a
`Vec<T>` is locked to a single concrete `T`, it cannot hold `Plain` and `Custom` together in the
same array. The three-element list's body ran three times too — this also falls into the
**every time** class, because the conversion is unavoidable for every element of the array.
What's worth noting is that the closure inside `map` never knows `e`'s real type at all:
the `e.name()` call does not say which body will run until reaching the list's third element —
only that element's own method table knows. This shows that even though the two call forms'
run count is equal, they do not do the same job — a trait object can carry diversity inside a
collection, static dispatch cannot.

#### Object Safety

Whether `dyn Label` compiles depends on `Label` being **object safe**. If a trait is not object
safe, a method table cannot be built for it, and the `dyn` syntax is rejected by the compiler.
There are two common obstacles: if a method returns `Self` **by value**
(something like `fn copy(&self) -> Self`), the table cannot know that method's return size in
advance — because the concrete type behind `dyn Label` can change at run time, and every type's
size can differ. If a method carries its own **type parameter** (something like
`fn convert<U>(&self, u: U)`), the table cannot hold a separate entry for infinitely many `U`s.
Both of these forms, described in prose here, are rejected — since they do not compile, they
are not put in a code block.

Both obstacles share the same root cause: every entry in a method table has to be a fixed,
known-size set of pointers at call time. If a method that returns `Self` by value were put in
the table, the caller could not answer, at compile time, the question "how much space will this
call's return value take up" — because behind `dyn Label` there could be `Plain`, or there
could be a type of another size entirely. A method carrying its own type parameter does not fit
into the table as a single entry either, because a separate body is needed for every different
`U`, and the table cannot enumerate infinitely many `U`s in advance. Both obstacles come down to
the same principle: a trait object is a reference that has **forgotten** the identity of the
implementing type; if a method depends on that forgotten identity (on `Self`'s exact size, or
on a `U` the caller chooses), the table cannot hold that method.

`Label` carries neither obstacle: `ledger` and `name` both take `&self` (not `Self` by value),
neither carries its own type parameter. This is why it is object safe, and the
`dynamic_dispatch` function above, with its `&dyn Label` parameter, compiles without a problem
— the accepted design's counterpart has already been run. Deciding which methods you want to
be usable as a trait object when writing a trait means shaping those methods' signatures around
these two rules from the start.

This condition sits in a place similar to the associated type constraint from the previous
lesson: both show that a contract might not be compatible with **every** way of using it. The
associated type constraint limited how many times a type could implement a contract; object
safety limits **which call forms** a contract can be used with. `Label` itself is not affected by
either, because it was kept simple from the start: it does not need a single associated type, and
it stays limited to object-safe methods. A trait writing a broader contract could run into one
or both of these constraints; which one it runs into can be read from the trait's signature,
before ever running it.

For example, a method that compares two values and returns the larger one **by value** —
`fn larger_of(&self, other: Self) -> Self` — would not be object safe: `Self` appears here as
both a parameter and a return type, and since the real type behind `dyn Label` can change at
run time, the table cannot fix this method's input and output size in advance. This method was
never added to `Label`; if it had been, `&dyn Label` would not compile. The same comparison
request could stay object safe by returning not a value but a **reference**, or an **ordering
result** (something fixed-size, like less, equal, greater) — and this ties into this course's
general observation too: a contract's boundaries are drawn not by the caller of that contract,
but by the **table** that is forced to work with the implementer's forgotten identity.

#### The Measurement's Limit

The same count does not mean the same cost. `static-dispatch` and `dynamic-dispatch` both running
twice does not mean the two call forms are **identical at run time** — the count only measures
how many times the body was **triggered**, not how the call was **found**. In a `static` call,
which body will run is determined by reading the source at compile time; in a `dynamic` call,
the same information is looked up from a method table at run time. Whether this lookup itself
is a step or not is a question the run counter cannot measure, because the counter only counts
how many times the `record` call passed, not how many steps were taken to reach that call. This
question was already addressed in the Object-Oriented Java course; this lesson does not ask it
again, it only shows that the two forms leave the **body count** equal.

This distinction has a concrete consequence: you cannot answer "does this source use dynamic
dispatch or static dispatch" just by looking at the run ledger. When the `static-dispatch` and
`dynamic-dispatch` rows sit side by side, both being two could lead a reader to a wrong
conclusion — "if they are doing the same thing, it does not matter which I choose." But it does
matter: the `list-body` row could only exist because the `dyn Label` form satisfies the object
safety condition; the same list could never be built with the bound `static` uses. Even though
the run count does not distinguish the two forms, which **notations** are possible still
diverges along a separate axis — this lesson shows that axis is seen in the code itself, not in
the count.

The course's first four lessons each asked, in turn, whether everything placed around a body —
a default body, a bound, an associated type, a call form — touches the run count. The answer
came out the same all four times: it does not. However the body is found (from a type parameter
or from a method table), it runs unavoidably everywhere it is called, and this is independent of
whichever syntax decision is layered on top of it.

#### Summary

- Static dispatch (`<T: Label>`) resolves at compile time, dynamic dispatch (`&dyn Label`)
  resolves at run time from a method table.
- Both forms fall into the every-time class and give the exact same run count: two and two —
  the call form does not change how many times the body is triggered.
- A trait object (`Box<dyn Label>`) can hold different concrete types in a single collection;
  static dispatch cannot, because a type parameter locks to a single concrete type.
- Object safety is the condition required for a trait to be usable with `dyn`: methods that
  return `Self` by value or carry their own type parameter break this condition.
- The same run count does not mean the same cost; whether the call is found at compile time or
  at run time is a separate question that cannot be measured by the run counter.

#### Next Step

In this lesson, the body itself was always hand-written — the `name` method's body came either
from the trait's default or from `Custom`'s own writing. A body can also be produced without
ever being hand-written. The next lesson shows how a body whose lines never appear in the
source runs at run time, and why this running cannot be counted by the run counter.

### Derived Traits

Source: https://academia.sh/en/courses/rust-abstraction/derived-traits

An attribute line can implement a trait without hand-writing it — derive; a hand-written body can place a counter call into the run ledger, while a derived body has no source line to count it in, so it can only be observed by its behavior — and the same derive gives two different results on two different data layouts.

The previous four lessons measured hand-written bodies: a trait's default body, a bounded
generic body, a body carrying an associated type, a body called statically or dynamically —
all of them stood somewhere in the source, under an `fn` line. A trait implementation is
sometimes never hand-written at all: the `#[derive(...)]` attribute line tells the compiler "you
generate this trait for this type," and produces the body without it ever being written in the
source. This lesson asks how many times a body of this kind runs — and shows that this time the
question cannot be answered with an exact number.

#### The Difference Between Hand-Written and Derived

An equality comparison for a type can be built two ways: by hand-writing the `impl PartialEq
for Type` block, or by placing the `#[derive(PartialEq)]` line above the type. Both produce the
same result — they say whether two values are equal field by field — but their counterpart in
the source is different.

```rust
// times.rs — slice: the record/times ledger, a hand-written equality body.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

// Hand-written implementation: the body sits in the source, it can be counted.
pub struct ManualEqual {
    pub x: i32,
    pub y: i32,
    pub ledger: Ledger,
}

impl PartialEq for ManualEqual {
    fn eq(&self, other: &Self) -> bool {
        record(&self.ledger, "manual-equality-body");
        self.x == other.x && self.y == other.y
    }
}

// Derived implementation: the body is not in the source, the compiler generates a field-by-field comparison.
#[derive(PartialEq)]
pub struct DerivedEqual {
    pub x: i32,
    pub y: i32,
}
```

`ManualEqual`'s `eq` method carries a two-line body in the source: first a record call, then a
field-by-field comparison. `DerivedEqual` carries no `impl PartialEq` block at all — its only
line is `#[derive(PartialEq)]`, and this line is not a method body, it is an **instruction** to
the compiler. When the compiler reads this instruction, it generates its own `eq` body; this
body does the same job as `ManualEqual`'s (comparing the `x` fields and the `y` fields), but it
never appears in any line of the source.

A single `#[derive(...)]` line can produce more than one trait at once; trait names separated
by commas are written inside the parentheses. `Clone`, already seen in this course, can be
derived this way too, and so can `Debug` (a trait that turns a value into a readable text
form):

```rust
// derive-debug.rs — a Debug derive turns fields into a text form automatically.
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let a = Point { x: 3, y: 5 };
    let b = a.clone();
    println!("{:?}", a);
    println!("is_equal={}", a == b);
}
```

```
Point { x: 3, y: 5 }
is_equal=true
```

All three traits (`Debug`, `Clone`, `PartialEq`) were derived on a single line, and none of the
three has a body visible in the source. The `{:?}` output shows that `Debug`'s generated body
gathers up the field names and values on its own — this pattern was not hand-written either, it
was generated by the compiler, just like `eq`. These three traits can be derived together
because all three rest on the same principle: apply the same operation (printing, cloning,
comparing) to every field of the type, in order. This same shared principle will come up again
in a later lesson (serialization).

#### Countable and Uncountable

```rust
// derive-uncountable.rs — a hand-written body can be counted, a derived body cannot.
mod times;
use times::{times, Ledger, ManualEqual, DerivedEqual};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let a = ManualEqual { x: 1, y: 2, ledger: Rc::clone(&ledger) };
    let b = ManualEqual { x: 1, y: 2, ledger: Rc::clone(&ledger) };
    let c = ManualEqual { x: 9, y: 9, ledger: Rc::clone(&ledger) };

    let is_equal = a == b;
    let is_not_equal = a == c;
    println!("manual: is_equal={} is_not_equal={}", is_equal, is_not_equal);
    println!("manual-equality-body={}", times(&ledger, "manual-equality-body"));

    // The derived PartialEq's body is not written in the source - so we cannot put
    // a record call inside it. We can only observe its behavior.
    let p = DerivedEqual { x: 1, y: 2 };
    let q = DerivedEqual { x: 1, y: 2 };
    let r = DerivedEqual { x: 9, y: 9 };
    println!("derived: is_equal={} is_not_equal={}", p == q, p == r);
}
```

```
manual: is_equal=true is_not_equal=false
manual-equality-body=2
derived: is_equal=true is_not_equal=false
```

**TG10.** `manual-equality-body` ran twice — both the `a == b` and `a == c` calls triggered the
`eq` body, because a record call was hand-placed inside the body. This row falls into the
**every time** class: the `==` operator runs unavoidably every time it is used. We cannot write
the same row for `DerivedEqual` — since we cannot add a single source line inside the body the
compiler generates, we cannot place a counter to show how many times it ran. We can see that
both the `p == q` and `p == r` calls give the correct result (a behavioral observation), but we
cannot write the count of how many times the body behind those two calls was triggered into the
run ledger. This shows that a body with no line written in the source **cannot be placed with
certainty into any of the course's three classes** — that it runs is certain (a call always
produces a result), but its run count cannot be measured with this method.

The Object-Oriented Java course showed that a record type's equality and text representation
are built automatically — what was measured there was which type made the decision, not
whether the record's own generated body runs. This lesson looks at a similar automatic
generation, but asks a different question: can this generated body be counted by the course's
counting-based method? The answer is no — the lesson says so openly, it does not make up a
number.

A derived body being uncountable does not mean it is random or unpredictable — quite the
opposite, what `#[derive(PartialEq)]` will produce is fully deterministic: it compares the
type's fields one by one in declaration order, `true` if all are equal, `false` if even one
is not. This rule is the same for every `#[derive(PartialEq)]`, and can be stated in advance just
by looking at the type declaration, without ever running it. What cannot be counted is not the
behavior itself, it is that behavior's run-time **call count**: to see how many times `eq` runs,
we need to add a line to the run ledger, and there is no line to add it to in derived source.

"The body is not in the source" does not mean "no body compiles." The `#[derive(...)]` line
**generates**, at compile time, a piece of code resembling an `impl` block, and this generated
code compiles the same way a hand-written `impl` block would — the generation happens in a
separate stage, before the rest of compilation. In the source the author sees, this generated
code never appears at all — only the `#[derive(PartialEq)]` line is visible, the dozens of
lines of comparison logic it produces are not. What this lesson means by "an unwritten body
still runs" is exactly this: something runs, it genuinely executes, but it has no counterpart in
the source the author **reads**. This is also why we cannot add a counter call — adding a counter
needs a source line to add it onto, and there is no such line in derived source.

#### Same Derive, Different Data Layout, Different Result

A derive having no body in the source might give the impression that it means the same thing
for every type. It does not: the same `#[derive(Clone)]` line produces two different behaviors on
two different data layouts. `derive` always says "apply the same operation field by field," but
what "the same operation" means depends on the field's own type — cloning an `i32` means writing
a new number, cloning an `Rc<...>` means incrementing a counter and sharing the same cell.

```rust
// derive-layout.rs — the same derive gives two different results on two data layouts.
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Clone)]
struct Linked {
    value: Rc<RefCell<i32>>,
}

#[derive(Clone)]
struct Separate {
    value: i32,
}

fn main() {
    let a = Linked { value: Rc::new(RefCell::new(1)) };
    let b = a.clone();
    *a.value.borrow_mut() += 41;
    println!("does the linked clone share: {}", *b.value.borrow() == 42);

    let c = Separate { value: 1 };
    let mut d = c.clone();
    d.value += 41;
    println!("does the separate clone share: {}", c.value == d.value);
}
```

```
does the linked clone share: true
does the separate clone share: false
```

**TG11.** `Linked`'s clone **shares** the value in the source: a change made through `a` is also
seen in `b`, because the derived clone of an `Rc<RefCell<i32>>` field just increments the
counter and hands back a second reference to the same cell — the behavior built in the
Ownership and the Memory Model course's Reference Counting lesson. `Separate`'s clone, on the
other hand, is **independent**: a change made on `d` never touches `c` at all, because the
derived clone of an `i32` field produces an independent copy on its own. The
`#[derive(Clone)]` line is exactly identical on both, but the result diverges by data layout —
derive is a shortcut for saying "clone field by field," it does not concern itself with what the
field itself means.

This distinction shows once more the difference between a hand-written body and a derived one:
a hand-written `impl Clone` could, if it wanted, be written to produce an independent copy for
`Linked` too — by reading the value inside the `RefCell` and building a new `Rc`. A derived body
does not offer this choice; it always says "clone the fields as they are," inheriting whatever the
field's own `Clone` implementation does. Whether a type's clone will be shared or independent is
decided not by the derive but by the choice of field types.

Derive also has a quiet precondition: writing `#[derive(PartialEq)]` for a struct requires that
**every field** of the struct itself implements `PartialEq` — the compiler calls `==` for every
field while generating the field-by-field comparison, and this call needs to compile, which
requires the field's own type to be comparable. `Point`'s `x` and `y` fields being `i32`, and
`i32` already carrying `PartialEq`, is why the derive compiles without a problem. If one of the
fields carried an incomparable type (a function pointer, say), the same `#[derive(PartialEq)]`
line would be rejected by the compiler — this rejection, too, by the course's general rule, is not
put in a code block; the `Point` and `ManualEqual` examples above already show the accepted
counterpart: structs carrying only comparable fields.

#### The Measurement's Limit

Derive does not mean the same thing on every type. The same `#[derive(Clone)]` can produce a
shared copy on one type and an independent copy on another; the same `#[derive(PartialEq)]` can
compare two fields on one type and ten fields on another — **what** the generated body does
depends on the fields of the type it is built on. This lesson does not count how many times a
derived body ran, and it does not assume that two different derives do the same job either; it
only shows that both are generated by the compiler and that their behavior can be observed by
running them. The course's measurement method hits a clear limit here: wherever a counter call
cannot be written in the source, **behavior comparison** takes over instead of a number.

This limit has a practical consequence: trusting a derived body requires understanding that
body's **design**, because there is no way to trace it at run time. Writing `#[derive(Clone)]`
for `Linked`'s `Rc<RefCell<i32>>` field makes the decision "let this type's clone be shared"
**invisibly** — this decision is not visible to someone reading the type declaration from the
`derive` line, it is visible from the field's own type. A hand-written `impl Clone` could show
this decision openly inside the body (is a reference being cloned, or a value); a derived body
hands this decision off to the field type and does not write it in the source separately. This is
the cost of derive's shortcut: the source gets shorter, but whoever reads the source has to go
down to the field types to understand the behavior.

The previous four lessons could measure the run count directly with a `record` call, because
the body always stood somewhere under an `fn` line in the source. This lesson breaks that
assumption: the body is not in the source, but the behavior is, and it can be verified. The
course's measurement method chooses to stay silent where it cannot count — instead of making up a
number, it says "there is no number here."

#### Summary

- The derive attribute (`#[derive(...)]`) implements a trait without hand-writing it, by having
  the compiler generate it; it leaves no method body in the source.
- A hand-written body (`manual-equality-body`) falls into the every-time class and can be
  counted — it ran twice in this lesson; a derived body runs every time it is called too, but
  since it has no body in the source, this run cannot be counted.
- An uncountable body's behavior can still be observed by running it: whether it gives the
  correct result can be seen, how many times it ran cannot.
- The same derive can give two different results on two different data layouts — it produces a
  shared copy on a type with an `Rc<RefCell<...>>` field, an independent copy on a plain
  numeric field.

#### Next Step

This lesson showed that a body runs even when it is **never written at all**. The course's last
lesson looks at another kind of invisibility: where an operator (like `+`) is called never
appears in the source, only the operator itself is visible. The next lesson counts how many
times this invisible call runs by running a three-term sum.

### Operator Overloading

Source: https://academia.sh/en/courses/rust-abstraction/operator-overloading

Every use of an operator (like +) is a trait call hiding behind it; a three-term sum calls it twice, a five-term sum four times — the operator's short syntax does not change the call count, it only changes whether the call is visible in the source, and this number can only be known from the run ledger.

The previous lesson showed that a body with no line written in the source still runs. This
lesson looks at the opposite kind of invisibility: the body is written in the source, but the
line that **calls** it is not. When `a + b` is written, a call is made, but there is no line
carrying the name `add` anywhere in the source — the call is hidden behind the `+` operator. The
Object-Oriented Java course already introduced operator overloading; what was measured there was
which type made the decision, what's measured here is how many times an operator's body
**runs**.

Every body measured across this course's five lessons could be referred to by a name in the
source: `name`, a bounded function, `produce`, `static_dispatch`/`dynamic_dispatch`, `eq`. In
operator overloading this naming disappears — only `+` appears in the source, the word `add`
never appears anywhere. This final lesson carries the course's measurement axis to its most
invisible application: to count how many times a body ran, you first have to **recognize** the
line that calls it in the source, and with operators this recognition step lies not in the
source itself, but in the language's syntax rule.

#### An Operator Is a Trait Call

In Rust, the `+` operator is not a fixed behavior, it is a call on the `std::ops::Add` trait. If a
type wants to be addable with `+`, it has to implement `Add`; if it does not, `+` does not
compile. The `a + b` syntax is **expanded** by the compiler into `Add::add(a, b)` — the shortcut
visible in the source carries an ordinary method call behind it.

This expansion is not in the same category as the derive from the previous lesson. A derived
body was never written in the source at all; the `add` body here is fully written, sitting line
by line inside the `impl Add for Length` block. What's invisible is not the body, it is the line
that **calls** the body — the `+` sign does not say it carries a call, it just places two values
side by side. This is the same even for the primitive integer and floating-point types: when
`3 + 4` is written, the same pattern runs under the hood, only for a primitive type like `i32`
this call is resolved through the compiler's own built-in rules, via the standard library's
`Add` implementation. For a user-defined type like `Length`, opening the same path means
implementing `Add` exactly.

```rust
// times.rs — slice: the record/times ledger, Length and the Add implementation.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

#[derive(Clone)]
pub struct Length {
    pub value: i32,
    pub ledger: Ledger,
}

impl Add for Length {
    type Output = Length;
    fn add(self, other: Length) -> Length {
        record(&self.ledger, "operator-body");
        Length {
            value: self.value + other.value,
            ledger: Rc::clone(&self.ledger),
        }
    }
}
```

The `impl Add for Length` block carries a notation familiar from the previous lesson: the line
`type Output = Length;` is the associated type covered in the Associated Types lesson — `Add`
itself carries the sum's result type within itself as a type. This lesson does not look at
`Output`, it looks at how many times the `add` body is called.

The `add` method's signature carries an observation of its own: `fn add(self, other: Length) ->
Length`. The parameter takes `self` itself (not `&self`) — the addition operation **consumes**
the two `Length` values, it does not borrow them. This is the ownership and move rule built in the
Ownership and the Memory Model course showing up again here: once `a + b` is written, neither `a`
nor `b` can be used after that expression, because both were moved into `add`. This is exactly
why `a` is cloned first in the `a.clone() + b` syntax below — since `a` will be used again in the
second sum (`sum + a`), it must not be consumed in the first sum.

#### Three Terms, Two Calls

```rust
// operator.rs — every use of an operator is a call; a three-term sum calls it twice.
mod times;
use times::{times, Ledger, Length};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

fn build(ledger: &Ledger, value: i32) -> Length {
    Length { value, ledger: Rc::clone(ledger) }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let a = build(&ledger, 3);
    let b = build(&ledger, 4);

    // Three terms, two '+' calls: (a.clone() + b) + a
    let sum = a.clone() + b;
    let sum = sum + a;
    println!(
        "three-term-sum={} operator-body={}",
        sum.value,
        times(&ledger, "operator-body")
    );

    // Five terms, four '+' calls - the same principle verified with a separate ledger.
    let ledger2: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let five: Vec<Length> = (1..=5).map(|d| build(&ledger2, d)).collect();
    let mut running_total = five[0].clone();
    for term in five.into_iter().skip(1) {
        running_total = running_total + term;
    }
    println!(
        "five-term-sum={} operator-body={}",
        running_total.value,
        times(&ledger2, "operator-body")
    );
}
```

```
three-term-sum=10 operator-body=2
five-term-sum=15 operator-body=4
```

**TG12.** The first measurement sums three terms (a clone of `a`, `b`, `a` itself), and
`operator-body` ran twice. The `+` sign also appeared twice in the source — `a.clone() + b` and
adding the result to `a` — and this appearance lines up exactly with how many times the body was
called. This row falls into the **every time** class: every use of the operator unavoidably
corresponds to a call. The second measurement confirms the same principle with a separate
ledger: when five terms are summed, the `+` written once in the source ran four times. The two
measurements together show a general rule: **a sum of n terms runs the operator body n minus one
times** — the first term is already in hand without needing any call, every remaining term
requires one call.

This rule comes from the structure of addition itself: summing two terms is one call, adding a
third term is a second call, adding a fourth is a third call. In the five-term loop this
accumulation starts from `five[0]` — zero calls so far, since no addition has happened yet — and
every new term adds one call, completing at four calls. The loop body in the source is a single
line (`running_total = running_total + term;`), but what matters is not how many times this line
was **written**, it is how many times it **ran** — a line written once in the source repeats
itself four times through the loop. This is an extra observation carried by the Operator
Overloading lesson: how many times a call is **written** in the source and how many times it
**runs** at run time can be different numbers, and a loop is the most common tool that pulls
these two apart.

The Object-Oriented Java course measured operator overloading's **which type made the
decision** — whether the declared type or the runtime type of the operands decides which
implementation is called for an operator. This course is not asking that question again; what's
measured here is how many times an already-determined implementation's body **runs**. In Rust
this determination already happens at compile time, read from `Length`'s concrete type — the
determination itself is not this lesson's subject.

#### The Syntax Is a Shortcut, the Call Is the Same

That the `+` syntax is a shortcut can also be verified by calling `Add::add` explicitly — if the
two notations expand to the same call, the trace both leave in the run ledger should be the
same.

```rust
// operator-equivalent.rs — the '+' syntax and the Add::add call run the same number of times.
mod times;
use times::{times, Ledger, Length};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
use std::rc::Rc;

fn build(ledger: &Ledger, value: i32) -> Length {
    Length { value, ledger: Rc::clone(ledger) }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let a = build(&ledger, 5);
    let b = build(&ledger, 6);

    // Two notations, the same call: one carrying '+', the other calling Add::add explicitly.
    let via_syntax = a.clone() + b.clone();
    let explicit = Add::add(a, b);

    println!("via_syntax={} explicit={}", via_syntax.value, explicit.value);
    println!("operator-body={}", times(&ledger, "operator-body"));
}
```

```
via_syntax=11 explicit=11
operator-body=2
```

**TG13.** `a.clone() + b.clone()` is one call, `Add::add(a, b)` is one call — both trigger the
same `add` body, and `operator-body` runs twice. This confirms that the `+` sign does not add any
new behavior on the compiler's part, it is only a way of shortening a call that already exists.
`Add::add(a, b)` carries the trait's name and method explicitly; `a + b` carries the same call
but hides the name. Choosing between the two has no effect at all on the run count, it only
affects the **readability** of the source.

`Add` is not alone; the `std::ops` module carries a similar trait for every binary operator —
`Sub` for subtraction, `Mul` for multiplication, `Index` for indexing access. All rest on the
same principle: a sign expands into a trait call. This lesson only measured `Add`, but the
counting method stays the same for all of them: every use is a call, and when chained, the call
count is one less than the term count.

This generalization is not an assumption — it comes from the same pattern being repeated across
the other traits, just like `Add`. `Sub` declares a single method named `sub`, `Mul` one named
`mul`, `Index` one named `index`, and each hides the same way behind its own sign (`-`, `*`,
`[]`). A type can also overload more than one operator — `Length` could have implemented both
`Add` and `Sub` if it wanted, each sitting in an independent `impl` block, and neither would
affect the other's run count. This lesson focused only on `Add` because the course's measurement
kernel already carried it; the principle stays the same for the remaining operators.

#### The Measurement's Limit

An operator body is an ordinary method, but **where it is called is invisible in the source**.
In `a.clone() + b`, the word `add` does not appear anywhere — the call hides behind the `+` sign.
A reader can answer "how many calls are in this expression" by counting the signs: every `+` is
a call. But as a chain grows longer, especially when produced inside a loop or an iterator
chain, counting the `+` signs in the source alone may not be enough either — how many times the
loop ran is also information you need to know. In the five-term loop above, only one `+` is
written in the source (`running_total = running_total + term;`), but since this line runs four
times inside the loop, the total call count is four. The number of signs in the source and the
run-time call count diverge here: **how many times it ran can only be known by counting, only by
the run ledger** — not by reading the source.

This is the final form of the question this course has asked from the start: how many times did
this body you wrote in the source run? In operator overloading this question shows up in its
most invisible form, because the call itself carries no name — only a sign. Of the sixteen
bodies measured across the course, the last one also stood behind this sign.

This invisibility confirms, from the other direction, an observation from the course's first
lesson. The Rust Design Goals lesson had shown that `HashSet::insert` and `Vec::push` do the
same job (adding an element) but fall into different classes when you look at their signatures —
the class could be determined by **reading** the source. In operator overloading, the class
(every time) can still be read from the source too — `+` is always a call — but the **count**
cannot be read from the source, because every `+` sign in the source can run more than once if
it is inside a loop. Throughout the course, the distinction "class is read from the source, count
comes from the run" is seen here in its clearest form.

These six lessons asked the same question in six different settings. Trait Definition and
Implementation showed that a default body written in the source runs for one type and never for
another. Trait Bounds showed that a condition placed around that body does not touch the run
count at all. Associated Types showed that two different notations of the same contract leave
the run count equal, the difference being in what the caller writes. Object Safety and Dynamic
Dispatch showed that whether the call is resolved at compile time or at run time does not change
the count either. Derived Traits showed that a body can run without ever being written in the
source, but that run cannot be counted. And this lesson showed that a written body's call can be
invisible in the source. Together, all six show that the question "how many times did it run" is
independent of visibility in the source: sometimes a visible body does not run, sometimes an
invisible call does.

#### Summary

- Using an operator (like `+`) corresponds to a method call (`Add`) in the standard library; the
  shortcut visible in the source carries an ordinary call behind it.
- The operator body (`operator-body`) falls into the every-time class: it runs twice in a
  three-term sum, four times in a five-term sum — the call count is always one less than the
  term count.
- The operator's syntactic brevity does not change the call count; calling `Length::add` by hand
  runs the same number of times as writing `+`.
- Since the call site is not visible in the source (only a sign is visible), how many times it
  ran can only be known by counting with the run ledger, not by reading the source.

This general rule — n terms, n minus one calls — holds not just for `Add`, but for every
notation that chains binary operators. If the chain's length is written once in the source and
repeated with a loop (as in the five-term example above), the only way to know the call count
in advance is knowing how many times the loop will run; counting the operator signs in the
source alone is not enough. This is the final example of a distinction the course has carried
from the start: the source says **what** will run, the run says **how many times** it ran, and
the two do not carry the same information.

#### Next Step

This course measured traits, bounds, associated types, dispatch forms, derived bodies, and
operator calls by asking when a body runs — every time, only when needed, or never. The next
topic carries the same question to places where a body can **fail**: how is a value's absence
expressed in the type, and does a body that handles that absence run every time, or only when
needed?

## Error Handling

Expressing absence in a type, carrying failure as a return value, shortening error propagation to a single mark, converting and chaining errors into their own types, and placing the unrecoverable case in a separate spot in the design.

### Expressing Absence with a Type

Source: https://academia.sh/en/courses/rust-abstraction/option

Carrying the possibility that a value is absent in a type, and measuring whether the fallback body that covers that value — depending on whether it is written eagerly or lazily — runs even while the value already exists.

The Operator Overloading lesson closed out the Traits and Generics topic by measuring
that every use of an operator is actually a call, and that the operator body ran twice in
a chained sum. That lesson showed that a body written in the source runs exactly as many
times as it is used — every use was an unavoidable run. This lesson carries the same
question to a completely different place: if a body runs only *when a value is missing*,
does it always run, or can it sometimes never run at all?

The Interfaces, Errors and Generics course established that an error is itself a return
value, one that can be carried without leaving the normal control flow; what it measured
there was *where* an error is carried — not in a signature, but in the return type. This
lesson inherits that decision, but looks at it from a different axis: how many times does
the body that covers a value's *absence* — the fallback that kicks in — run? This topic's
(`error-handling`) five lessons apply this question in turn to absence, failure, the
shorthand, conversion, and the unrecoverable case; this first lesson looks only at
absence, and failure — which carries a reason — comes in the next lesson.

Throughout the Traits, Generics and Error Handling course, the same question stays the
same: how many times does a body written in the source run at execution? The Traits and
Generics topic asked this question of a behavior's implementations; in this topic the
question goes to the two types that carry an operation's outcome — `Option` and
`Result` — and to the shorthand, conversion, and unrecoverable case that follow them. The
question itself does not change, only where it is applied does.

#### A Type for Absence

In Rust, `Option<T>` is used everywhere a value might not be present. It is the standard
library's own instance of the enum mechanism set up in the previous course: it has two
variants, `Some(T)`, which carries the value, and `None`, which carries none at all. If a
function's return type is `Option<i32>`, the caller must handle both cases at compile
time — accidentally trying to read a value out of `None` is not structurally possible in
this type.

**EH1.** This differs from carrying the "no value" case as a separate sentinel value (a
zero, say, or an empty string). A sentinel value can be confused with a real one; `None`,
by contrast, is a state completely separate from the `T` type that `Some(T)` carries, and
the compiler never confuses the two. This lesson does not re-establish that
distinction — it was already covered in the Ordered and Set Structures lesson — it only
looks at how many ways there are to *extract* a value from an `Option`, and how many
times each of those ways runs the body written in the source.

`Option` is mostly not built by hand; it arises as the natural result of a lookup. When an
ordered map is asked for a key's value, it returns `Some(value)` if the key is there and
`None` if it is not — the caller never has to ask "does the key exist" separately,
because the lookup itself produces a value that carries that information. The same
pattern holds for an out-of-bounds index into an array: direct indexing panics on an
invalid index, but a `get` call returns an `Option`, and the caller handles the
out-of-bounds case as an ordinary `None`. The examples used in this lesson do not
simulate these lookups; they build `Some` and `None` values directly, because what is
measured is not the lookup itself but the way a value is extracted from its result.

The Pattern Matching lesson established that a `match` expression must cover every
arm — the exhaustiveness principle. A `match` written by hand over an `Option` follows
the same principle: both the `Some` and `None` arms must be written, and if either is
left out, the source does not compile at all. `unwrap_or` and `unwrap_or_else` are
shortcuts that fold this two-armed `match` into a single method call — the
`Some(value) => value, None => fallback` form itself does not appear in the source, but
the code the compiler generates still branches into the same two arms. This lesson
measures how many times those two arms run, directly through these shortcuts, without
writing the `match` form again.

The same exhaustiveness principle applies to the `if let` form too, but in a different
way: `if let Some(value) = option { ... }` writes only the `Some` arm and silently skips
the `None` case — this does not violate exhaustiveness, because `if let` is already
defined by the language itself as a *partial* form of `match`. `unwrap_or` and
`unwrap_or_else` stand at the opposite end: both cover both arms, and they differ only in
how the `None` arm's body is written in the source. This lesson does not measure
`if let`, because it has no "arm that does not run" in the source to begin with — the
difference being measured is how differently two methods that both write both arms
actually run.

#### Eager and Lazy Defaults

The two most common ways to pull a value out of an `Option` both supply a fallback that
kicks in when the value is missing. That fallback can be written in two forms: directly,
as a value (`unwrap_or`), or as a call that returns a closure (`unwrap_or_else`). Both
read as if they do the same job, but when their arguments are evaluated differs.

The Rust's Design Goals lesson established that an expression is evaluated to produce a
value; a function call's argument is itself an expression, and it is evaluated before the
call happens. The form `unwrap_or(eager_default(&ledger))` is not exempt from this rule:
`eager_default(&ledger)` is an argument expression, and it is computed before `unwrap_or`
itself runs — even though it looks as if there is a branch inside `unwrap_or` checking
whether it is `Some` or `None`, the argument has already been evaluated before that
branch. `unwrap_or_else`, on the other hand, takes not a *value* as its argument but a
*closure*; the closure itself does not run when it is built, only when
`unwrap_or_else`'s own internal implementation enters the `None` arm is it called.

In the run below, `full` and `empty` are built by hand, but both represent the two
outcomes a lookup can return: `full` stands for a lookup whose key was found, `empty` for
one that was not. The bodies being measured — the `eager_default` function and two
closures — each report their own run by dropping a record into the counter ledger. The
run below shows this difference directly.

```rust
// absence_ledger.rs — body-counting core: the Ledger/record/times slice of times.rs
pub type Ledger = std::rc::Rc<std::cell::RefCell<std::collections::BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}
```

```rust
// absence_measurement.rs — how many times the eager and lazy defaults run
mod absence_ledger;
use absence_ledger::{Ledger, record, times};
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BTreeMap;

fn eager_default(ledger: &Ledger) -> i32 {
    record(ledger, "eager-default");
    -1
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let full: Option<i32> = Some(7);
    let empty: Option<i32> = None;

    let a = full.unwrap_or(eager_default(&ledger));
    let b = full.unwrap_or_else(|| {
        record(&ledger, "lazy-default-unneeded");
        -1
    });
    let c = empty.unwrap_or_else(|| {
        record(&ledger, "lazy-default-needed");
        -1
    });

    println!("full.unwrap_or(...) result: {}", a);
    println!("full.unwrap_or_else(...) result: {}", b);
    println!("empty.unwrap_or_else(...) result: {}", c);
    println!();
    for name in ["eager-default", "lazy-default-unneeded", "lazy-default-needed"] {
        println!("{:<28}{} times", name, times(&ledger, name));
    }
}
```

```
full.unwrap_or(...) result: 7
full.unwrap_or_else(...) result: 7
empty.unwrap_or_else(...) result: -1

eager-default               1 times
lazy-default-unneeded       0 times
lazy-default-needed         1 times
```

Even while `full` is full, `eager_default` still runs once: `unwrap_or`'s argument is
evaluated as an ordinary function call before the call itself happens. Whether the
`Option` is `Some` or `None` makes no difference here — because the argument is an
expression, it is computed regardless of whether it will be used. **EH2.** That is why
the `eager-default` line falls into the "every time" class: whenever `unwrap_or` is
called, its argument is unavoidably run as well.

The closure given to `unwrap_or_else`, by contrast, behaves the opposite way. Called on
`full`, its body never runs at all — `lazy-default-unneeded` stays at zero — because the
closure is only called in the `None` case. Called on `empty`, that same closure pattern
actually runs, and `lazy-default-needed` climbs to one. The difference lies in the
argument's kind: one is a ready-made value, the other a call that *produces* the value
only when needed. `lazy-default-needed` falls into the "only when needed" class — it runs
exactly as often as the absence actually occurs.

What the Interfaces, Errors and Generics course measured was *where* an error is bound:
in a signature, at the call site, or inside the value. This lesson does not ask that
question — `Option` itself already belongs to the same family as that course's third
option, a state carried inside a value. What is measured here is how many times the body
that covers that state runs; even though `eager-default` and `lazy-default-needed`
produce the same count (one), one runs unconditionally and the other only when it is
truly needed.

The `_or` and `_or_else` suffixes in the method names label this distinction: the `_or`
suffix marks methods where a ready-made value is given directly as the argument; the
`_or_else` suffix marks methods where a closure is given in its place. This naming
pattern is not unique to `Option` — the standard library uses the same `_or` / `_or_else`
pair on other types too, and the meaning stays the same in both: `_or` is eager,
`_or_else` is lazy. The next lesson will meet this naming pattern again on `Result`.

The `Option` type's standard-library methods split along this same axis. Ones like
`unwrap_or` that want a ready-made value always run; ones like `unwrap_or_else` that want
a closure run only when needed. A chaining method like `and_then` stands only on the
closure side: the closure it is given runs only in the `Some` case and is never called on
`None`. `map_or` combines both in a single call — its first argument is a ready-made
value and is evaluated every time, even on `None`; its second argument is a closure and
runs only in the `Some` case; inside one single method call, the two classes this lesson
measures can be seen side by side. A method name not ending in `_else` does not mean this
distinction stops applying; exactly which argument each method runs and when is
documented for every method, and it can be read through the same axis measured in this
lesson.

#### A Limit on the Measurement

The count difference above does not mean the lazy form is always the better choice. The
difference is only in *how many times it runs*; if the body is cheap, the outcome of both
forms is indistinguishable.

```rust
// cheapness.rs — when the body is cheap, both forms produce the same result, only the count differs
mod absence_ledger;
use absence_ledger::{Ledger, record, times};
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BTreeMap;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let full: Option<i32> = Some(9);

    let a = full.unwrap_or({
        record(&ledger, "cheap-eager");
        0
    });
    let b = full.unwrap_or_else(|| {
        record(&ledger, "cheap-lazy");
        0
    });

    println!("eager form's result: {}", a);
    println!("lazy form's result: {}", b);
    println!("cheap-eager body: {} times", times(&ledger, "cheap-eager"));
    println!("cheap-lazy body: {} times", times(&ledger, "cheap-lazy"));
}
```

```
eager form's result: 9
lazy form's result: 9
cheap-eager body: 1 times
cheap-lazy body: 0 times
```

**EH3.** Both lines give the same result (`9`); the program is not affected by which form
is chosen. The only thing that diverges is whether the body runs: `cheap-eager` runs
once, `cheap-lazy` never runs. Because the body here only returns an integer, this
difference changes nothing; but if the body were an expensive operation — a file read, a
network call, a large computation — the same difference would turn into a cost that can
no longer be overlooked. This lesson does not measure that cost — it only counts *how
many times it runs*; the size of the cost depends on the body written in the source
itself, and this lesson does not evaluate that.

This is where the measurement's real limit shows up. If a program fills in a missing
field from a settings record with a fixed integer, there is no visible difference between
the eager and the lazy form — both produce the same number, and the only difference is as
small as the counter record seen in the run above. If that same program instead fills the
missing field by reading a file or computing a sum, the eager form runs that read or
computation even while the value already exists — the result still does not change,
because `unwrap_or` will not use the computed value anyway, but the body still runs. This
lesson does not decide which body counts as "cheap" and which as "expensive"; it only
counts how many times each form runs and leaves that decision to whoever reads the
source. The count alone does not say that one form is "better" than the other — it only
makes visible what runs and what does not.

In both runs above, the "eager" side was always written as a separate function, but that
is not required. The block in the form `unwrap_or({ record(&ledger, "cheap-eager"); 0 })`
is itself an expression too; it is evaluated as the argument even without carrying a
separate function name. The limit being measured is therefore not specific to a function
call — no matter how `unwrap_or`'s argument is written, as long as it is an expression, it
is evaluated before the call happens. The only difference is readability: a separate
function name names what the body does; an inline block leaves the same work unnamed.

These three runs — the eager/lazy comparison, the cheap-body comparison, and the naming
difference between them — together arrive at a single conclusion: which method is chosen
for extracting a value from an `Option` is a question independent of the result. The
result is the same in both forms; what diverges is only whether the body written in the
source runs, and how many times. This distinction may look like a small detail, but it
will be met again in the same form throughout the next four lessons: depending on which
method of a type is chosen, the same body in the same source sometimes runs unavoidably,
sometimes only when needed, and sometimes not at all.

#### Summary

- `Option<T>` carries the possibility that a value is absent as a separate type;
  `Some(T)` and `None` are handled separately at compile time, and a `match` must cover
  both under the exhaustiveness principle.
- The argument given to `unwrap_or` is an expression and runs **every time**, even while
  the value already exists — it does not look at which variant the `Option` is.
- The closure given to `unwrap_or_else` runs only in the `None` case: it runs **never**
  while the value exists, and **only when needed** once absence actually occurs.
- Some methods, like `map_or`, combine both classes in a single call — one argument runs
  every time, the other only when needed.
- If the body is cheap, the result of both forms is the same; the difference is only in
  the run count, and it matters more as the body gets more expensive.
- The `_or` / `_or_else` naming pattern marks the eager/lazy distinction across the
  standard library; it will appear again on `Result` in the next lesson.

#### Next Step

This lesson measured the case where a value might be *absent*. But when an operation
fails, saying just "absent" is usually not enough — the *reason* for the failure has to
be carried too. `None` has no reason; it does not say why an operation failed, only that
a value was not found. The Failure as a Return Value lesson applies the same "how many
times did it run" question to a type that now also carries a reason: when the success
and failure arms are written as separate bodies, and the same function is called several
times in a row, which of those arms runs in a given run, and how do the arm counts
diverge while the total call count stays fixed? This topic's name (`error-handling`)
starts earning its real meaning from that lesson on: what is measured is no longer just a
value's absence, but a named failure.

### Failure as a Return Value

Source: https://academia.sh/en/courses/rust-abstraction/result

Writing an operation's success and failure paths as separate bodies in the source, and measuring how the run counts of these two arms diverge while the total call count stays fixed as the same function is called repeatedly.

`None` has no reason — it says that a value was not found, but not why. When a file-open
call fails, what the caller needs to know is not just "no result"; it also needs to know
whether the file was not found, permission was denied, or something else happened. This
lesson measures how many times, and which arm, a result that also carries a
reason — `Result<T, E>` — runs.

The axis measured in this topic's first lesson (whether it runs eagerly or lazily) does
not hold here; there is no relationship like "one is the cheaper form of the other"
between `Ok` and `Err`. Instead, a new question comes into play: of two different bodies
sitting side by side in the source, which one runs depends on the input — and how does
that vary, and accumulate, over the course of a run?

The Interfaces, Errors and Generics course established that an error is placed in the
third of its three binding kinds — *inside the value*: the error is carried neither in a
signature nor at a call site, but directly in the return value itself. This lesson
inherits that decision — `Result` follows exactly this arrangement — but asks a different
question: when the same function is called repeatedly, how many times does each of the
success and failure arms run?

The Expressing Absence with a Type lesson measured that the eager and lazy forms do *the
same job*, differing only in how many times they run. The difference measured in this
lesson sits somewhere else: the `Ok` and `Err` arms do not do the same job — they run two
entirely different bodies. The question is no longer "how many times does the same job
run" but "which job, how often" — what share of the run belongs to each of the two
different bodies written in the source.

#### A Result, Two Paths

`Result<T, E>` is another standard-library instance of the enum mechanism set up in the
previous course: `Ok(T)` carries the successful result, `Err(E)` carries the reason for
failure. Its difference from `Option` is that, unlike `None`, `Err` carries a value of
its own — failure here is not an empty state, but data with its own type. `Option`
answers the question "is there a value"; `Result` can also answer "why did the operation
fail."

**EH4.** These two types use the same enum mechanism, but they carry different
information: `Option`'s `None` carries no data, `Result`'s `Err` does. This also means the
two types cannot substitute for each other: `Option` is enough to say whether a lookup's
result was found; `Result` is needed when it is necessary to say why an operation failed.
A codebase can see both together — a `Result<Option<T>, E>`, for instance, carries both
the possibility that the operation failed and the possibility that, even on success, no
value was found; this combination is not measured in this lesson, but it clarifies why
the two types are kept separate. If a function's return type is `Result<i32, NumberError>`,
the caller does not just ask "did it fail" but can also read which error occurred. The
`NumberError` used in this lesson is an empty struct that carries no fields — the error
itself does not matter in this lesson, what matters is which arm runs when. The Custom
Error Types lesson will focus on the error itself, on the information it carries.

In the `match` above, the `Err` arm does not bind the error itself to a name; it writes
`Err(_)` — the underscore is a form of the catch-all pattern established in the Pattern
Matching lesson, and it is the source's way of saying "I will not read this value."
Because `NumberError` carries no fields, there is nothing to read here anyway; once the
error starts carrying information in the next two lessons, `Err(_)` will give way to a
pattern that actually binds the error.

`Option`'s `_or` / `_or_else` naming pattern continues on `Result` too: `unwrap_or`,
`unwrap_or_else`, `map_or`, `and_then` — all of them have a `Result` counterpart, and all
of them carry the same eager/lazy distinction. But this lesson does not measure those
methods; it measures the side that is unique to `Result`, the case where the two arms
**do different jobs**. In a method like `unwrap_or`, both "sides" would do the same job
of the same type (producing a value); here the `Ok` arm continues a computation, and the
`Err` arm stops the computation and produces a reason — two different jobs.

A function returning `Result` does not mean that function must resolve its own error
internally. `resolve` produces its own `Err` and returns it; the caller can decide to use
that value, ignore it, or carry it unchanged into its own return value. This third
option — carrying an error upward without processing it — can cross several function
boundaries in a row, and does not require writing a new arm in the source at each
crossing. This lesson does not measure that carrying; it only counts, at a single
boundary, over a single `match`, how many times the two arms run. The carrying itself is
the subject of the Shorthand for Error Propagation lesson.

#### Which Arm Runs

When a function returns `Result`, the caller usually separates the two arms with a
`match`. Both arms are ordinary Rust code — neither runs through a separate mechanism
from the other, both are part of the same `match` expression that looks at the same
function's return value. This means both arms sit side by side in the compiled binary,
and which one runs depends only on the returned value (`Ok` or `Err`). The run below
calls the same function with three different inputs and counts which arm runs on each
call.

```rust
// error_check.rs — success and failure arms as separate bodies
pub type Ledger = std::rc::Rc<std::cell::RefCell<std::collections::BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

#[derive(Debug)]
pub struct NumberError;

pub fn resolve(text: &str) -> Result<i32, NumberError> {
    match text.parse::<i32>() {
        Ok(number) => Ok(number * 2),
        Err(_) => Err(NumberError),
    }
}
```

```rust
// two_arms.rs — the same function is called three times, counting which arm runs
mod error_check;
use error_check::{Ledger, record, times, resolve};
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BTreeMap;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    for text in ["8", "twenty-one", "15"] {
        match resolve(text) {
            Ok(result) => {
                record(&ledger, "success-arm");
                println!("{:<10} success arm, result {}", text, result);
            }
            Err(_) => {
                record(&ledger, "failure-arm");
                println!("{:<10} failure arm", text);
            }
        }
    }

    println!();
    let total = times(&ledger, "success-arm") + times(&ledger, "failure-arm");
    println!("total calls: {}", total);
    println!("success-arm: {} times", times(&ledger, "success-arm"));
    println!("failure-arm: {} times", times(&ledger, "failure-arm"));
}
```

```
8          success arm, result 16
twenty-one failure arm
15         success arm, result 30

total calls: 3
success-arm: 2 times
failure-arm: 1 times
```

`resolve` is called three times, and the total call count is fixed: three. But these
three calls' arms are not distributed the same way: two calls fall to `success-arm`, one
call to `failure-arm`. **EH5.** This shows that the two bodies written in the
source — the `Ok` arm's body and the `Err` arm's body — run independently of each other:
while the total stays fixed, which arm is chosen how many times depends only on the
input. The text `"twenty-one"` cannot be parsed as an integer, so the `Err` arm runs;
`"8"` and `"15"` can be parsed, so the `Ok` arm runs. Both arms are written at equal
length in the source, but how much each one runs in a given run is a number that depends
on the input.

This distinction sits in a different place from what was seen with `Option`. The
difference measured in the previous lesson was between an *eager* and a *lazy* body —
both could do the same job, and they differed only in run timing. Here, the two arms do
entirely different jobs: one doubles the result, the other produces an error value. The
count itself does not change this difference — the `times` function does not know what
job each arm does, it only counts how many times it was called. The measurement stays
the same here too: how many times a body written in the source runs in a given run.

These three inputs stand in for a validation pipeline: three separate fields entered by a
user, parsed in turn. In a real validation pipeline, inputs do not arrive one at a time
but as a list, and the same `resolve` call repeats for every field. The `for` loop above
shows this pattern at a small scale: three pieces of text pass through the same function,
and each one selects its own arm. The loop itself is not a single "run" — it is the sum
of three separate calls. What matters for this lesson is that each step of the loop is an
independent call, and each call selects its own arm without being affected by earlier
calls: the second call returning `Err` has no effect whatsoever on which arm the third
call selects. The counter ledger naturally preserves this independence — each `record`
call only increments its own key and touches no other key.

The `match` expression itself carries a principle established earlier, by the Control
Flow lesson: `match resolve(text) { ... }` is an expression, and it produces a
value — here, the value produced is `()`, because both arms only produce a side effect (a
counter record and a print) without visibly returning a value. This shows again the
difference between `match` itself being an expression and what value its arms produce:
the Control Flow lesson established that `match` is an expression and that all its arms
must yield the same type; here every arm produces `()`, but the body *inside* each arm is
entirely different from the other's. An expression itself may produce a single value,
even while the code that runs to reach that value diverges arm by arm.

#### A Limit on the Measurement

Both arms are written in the source, but only one of them runs in a given call. The run
below makes this concrete with a single call.

```rust
// single_call.rs — both arms are written in the source, but only one runs in a single call
mod error_check;
use error_check::{Ledger, record, times, resolve};
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BTreeMap;

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    match resolve("100") {
        Ok(result) => {
            record(&ledger, "success-arm");
            println!("result: {}", result);
        }
        Err(_) => {
            record(&ledger, "failure-arm");
            println!("an error occurred");
        }
    }

    println!("success-arm: {} times", times(&ledger, "success-arm"));
    println!("failure-arm: {} times", times(&ledger, "failure-arm"));
}
```

```
result: 200
success-arm: 1 times
failure-arm: 0 times
```

**EH6.** Because the `resolve("100")` call gives an `Ok` result, only `success-arm` runs;
`failure-arm` stays at zero. But this zero does not mean the `Err` arm *does not
exist* — that arm stands in the compiled binary, the compiler produced it, `match` was
required to cover it under exhaustiveness. It was never selected in this
particular run. An arm being written in the source and an arm having run in a given run
are two separate facts: one is a property of the source code, the other is the result of
a particular run made with a particular input. This distinction between "covered
code" — code the compiler accepts, satisfying the exhaustiveness principle — and
"executed code" — code actually run by the processor in a particular execution — also
summarizes the difference between reading a program's source and measuring one of its
runs.

The importance of this distinction becomes clearer when you consider what the
exhaustiveness principle actually provides. Without the exhaustiveness principle — that
is, if `match` allowed writing only the `Ok` arm and ignoring the `Err` case — a body
called `failure-arm` might never exist in the source at all, and when an input like
`"twenty-one"` arrived, the program would fall into an undefined state: no error value
would be produced, and the flow would not know where to go. The exhaustiveness principle
prevents this — the `Err` arm *must* exist in the source, whether it never runs in a
given run or takes over the entire run. Having run zero times does not mean it was not
needed there; on the contrary, the reason zero can even be measured is that the arm is
already there, standing ready to run.

This difference may not look significant in a small example, but it gains importance as
the number of call sites for a function grows. If a function is called thousands of times
and most of the inputs are valid, the `Err` arm still takes up space in the source, gets
compiled, gets checked — but may almost never run in a given run. The reverse is also
possible: if a function is called mostly with invalid inputs, the `Ok` arm may stand in
the source but run rarely in a given run. The source itself does not say which arm will
run more often; only an actual run can say that.

In the second run above, two of the three inputs succeeded and one failed —
`success-arm` ran twice, `failure-arm` once. If the same function were called with a
thousand inputs and only a few of them were invalid, the ratio would shift accordingly:
`success-arm` could sit near a thousand while `failure-arm` stayed in the single digits.
The length of the two arms written in the source has nothing to do with this ratio — the
`Err` arm could be a one-line body and still take over nearly the entire run, if the
inputs call for it. This lesson does not try to predict that ratio; it only shows that
the ratio must be read from a run, not from the source.

The two runs above together partly answer the question asked at this topic's opening: in
`Option`, whether the fallback body ran depended on whether it was written *eagerly or
lazily*; in `Result`, whether an arm runs depends on which input it corresponds to. Both
fit the same measurement frame — how many times a body written in the source runs in a
given run — but each falls into the "every time," "only when needed," or "never" class
for its own reason.

#### Summary

- `Result<T, E>`, unlike `Option`, also carries the *reason* for failure: `Ok(T)` and
  `Err(E)` hold separate information; `Option`'s `_or` / `_or_else` naming pattern applies
  here too.
- The success and failure arms' bodies are written separately in the source and run
  independently of each other; when the same function is called repeatedly, the **total
  call count stays fixed**, but the arm counts diverge with the input.
- An arm being written in the source does not mean it runs on every run — in a single
  call, only one of the two arms runs, and the other runs **never** in that run.
- "Covered code" (code the compiler accepts, written to satisfy exhaustiveness) and
  "executed code" (code that actually runs in a given run) are two separate facts.
- A catch-all pattern like `Err(_)` is enough to count which arm ran without reading the
  error's content; the information an error carries is the subject of the next lessons.
- How often each arm will run cannot be read from the source; it can only be read from a
  run made with real inputs. In this run, both arms ran **only when needed**: each call
  ran the arm its own input required, and the other one ran **never** on that call.

#### Next Step

In both runs above, the success arm repeated the same three lines: parse the value,
report if there is an error, otherwise continue. This pattern quickly turns into one that
repeats in a function that does several parses in a row — especially when a function has
to call another parse internally and check its result too, the same `match` skeleton
gets written several times in a row. The Shorthand for Error Propagation lesson measures
a form that shortens this pattern to a single mark — and shows that the shortcut shortens
the source but does not change the run, meaning `success-arm` and `failure-arm` give the
same counts seen in this lesson. The shorthand reduces not the count being measured, but
the number of lines that must be written to reach that count — this lesson built the
`match` skeleton by hand, the next lesson measures how much the same skeleton can be
shortened in the source without changing the run.

### The Shorthand for Error Propagation

Source: https://academia.sh/en/courses/rust-abstraction/the-question-mark-operator

Measuring that the shorthand which reduces checking an error and returning early to a single mark gives exactly the same counts as the same job written by hand with a branch — that it shortens the source without changing the run.

The Failure as a Return Value lesson showed that when the same function is called
repeatedly, the success and failure arms run independently of each other, and both
repeat the same three-line pattern inside the same `match` body: parse the value, report
if there is an error, otherwise continue. If a function does more than one parse, this
pattern repeats in the same way each time — the same two-armed `match` after every parse.
This lesson measures the shorthand that reduces this repetition to a single mark: the
question mark (`?`).

As this topic's third lesson, this lesson now assumes that success and failure are
separate bodies — that distinction was established in the previous lesson. The question
asked here goes one step further: how much can the `match` skeleton that separates these
two bodies be shortened in the source, and does that shortening change the counts in the
run? The answer emerges by comparing the two runs below.

The Interfaces, Errors and Generics course established that an error is carried as a
return value and does not leave the normal control flow. The question mark does not
break that decision either — it still produces a return value, still uses an ordinary
function return — it only shortens how long it takes to write that return in the source.
This lesson does not return to the *where* question that course measured; what it
measures is whether the shorthand changes the run.

The Functions and Expression Orientation lesson measured when a function needs to return
early before its body finishes: if the rest of the body will not be computed, an
explicit `return` made that fact visible in the source. The question mark uses exactly
this early return — only instead of writing the `return` form by hand, it places a
single mark right after a `Result` call. This lesson does not re-establish the
early-return concept that lesson set up; it only measures whether this particular
shorthand gives *the same counts* as a hand-written early return.

#### Same Counts, Shorter Source

The question mark is written right after a call that returns a `Result`, inside a
function that itself returns a `Result`. If the call returns `Ok`, the value inside it is
unwrapped and the body continues from where it left off; if it returns `Err`, the
function returns immediately with its own `Err`, and the rest of the body never runs.

This shortcut has one condition: the question mark can only be written when the function
it is inside also returns a `Result` (or `Option`). If `main`'s return type is `()`, `?`
cannot be placed directly after a call that produces a `Result` inside `main`'s body —
there is no `Result` slot for the early return to return into. This lesson does not
violate that condition: `resolve` carries its own `Result` return type, and `?` is used
only inside its body; the calls inside `main`, meanwhile, bind the returned `Result` to a
variable and print it with `{:?}`, without using `?` themselves.

In the `resolve` function below, `text.parse()` returns a `Result<i32, ParseIntError>`,
but the function's own return type is `Result<i32, NumberError>` — two different error
types. The question mark resolves this difference on its own: thanks to the
`impl From<ParseIntError>` implementation written for `NumberError`, the error `parse`
produces is automatically converted into `NumberError`. This conversion itself is the
subject of the Custom Error Types lesson; in this lesson, only the fact that `?` *uses*
this conversion is visible, and it does not affect the count measured in this lesson.

```rust
// with_question.rs — the resolve slice of times.rs: the body after the question runs once on success, never on failure
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

#[derive(Debug)]
struct NumberError;

impl From<std::num::ParseIntError> for NumberError {
    fn from(_: std::num::ParseIntError) -> NumberError {
        NumberError
    }
}

fn resolve(ledger: &Ledger, text: &str, label: &str) -> Result<i32, NumberError> {
    let number: i32 = text.parse()?;
    record(ledger, label);
    Ok(number * 2)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let success = resolve(&ledger, "21", "after-question-success");
    let failure = resolve(&ledger, "twenty-one", "after-question-failure");

    println!("success call: {:?}", success);
    println!("failure call: {:?}", failure);
    println!();
    for name in ["after-question-success", "after-question-failure"] {
        println!("{:<24}{} times", name, times(&ledger, name));
    }
}
```

```
success call: Ok(42)
failure call: Err(NumberError)

after-question-success  1 times
after-question-failure  0 times
```

**EH7.** In the `resolve(&ledger, "21", ...)` call, `text.parse()` successfully produces
`21`; the question mark unwraps this value, binds it to `number`, and the body continues
from where it left off — the `record` call runs, and the `after-question-success` line
increases by one. In the `resolve(&ledger, "twenty-one", ...)` call, by contrast, `parse`
produces an error; the question mark converts this error into `NumberError` and returns
from the function immediately. The `record` call never gets its turn —
`after-question-failure` stays at zero. This is the same behavior as the `Ok`/`Err` arms
in the previous lesson: one runs, the other does not. The difference is that these two
arms are no longer written in a separate `match`, but are now hidden behind a single
mark.

Looking at the `resolve` function's return type, only one line in its body
(`let number: i32 = text.parse()?;`) looks related to `Result`; the remaining two lines
(the `record` call and `Ok(number * 2)`) read like ordinary code. This shows how small a
trace the question mark leaves in the source — no matter how many separate parses a
function does, it completes the error check by adding a single character after each one.
The `label` parameter carrying the name passed to `record` lets this lesson track two
different calls (success and failure) through the same function but with separate
counter keys; in real usage this parameter would not be present, it was added only for
measurement.

The same function can also be built with a hand-written branch. The run below replaces
the question mark with a `match` and an early `return`, and is called with the same two
inputs.

```rust
// manual_branch.rs — the same job written with a hand-written branch gives the same counts
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

#[derive(Debug)]
struct NumberError;

fn resolve_by_hand(ledger: &Ledger, text: &str, label: &str) -> Result<i32, NumberError> {
    let parsed = text.parse::<i32>();
    let number = match parsed {
        Ok(value) => value,
        Err(_) => return Err(NumberError),
    };
    record(ledger, label);
    Ok(number * 2)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let success = resolve_by_hand(&ledger, "21", "after-question-success");
    let failure = resolve_by_hand(&ledger, "twenty-one", "after-question-failure");

    println!("success call: {:?}", success);
    println!("failure call: {:?}", failure);
    println!();
    for name in ["after-question-success", "after-question-failure"] {
        println!("{:<24}{} times", name, times(&ledger, name));
    }
}
```

```
success call: Ok(42)
failure call: Err(NumberError)

after-question-success  1 times
after-question-failure  0 times
```

**EH8.** The two runs give exactly the same counts: `after-question-success` one,
`after-question-failure` zero. The source differs — one line (`text.parse()?`) in the
first, a five-line `match` in the second — but the run is the same. The question mark
does not add new behavior; it does the same job as the compiler-generated code of a
hand-written `match` and early `return`, by writing less. This is direct proof that the
shorthand shortens the *source*, not the *run*: the two programs have source of
different length, but produce the same binary behavior.

The `match` in `resolve_by_hand` differs from the `match` in the previous lesson in one
respect: the `Err` arm does not produce a value and hand it off to the rest of the body,
it exits the function directly with `return Err(NumberError)`. This does not contradict
the principle established in the Control Flow lesson that `match` is an expression —
`return` is not an expression but a statement, and a statement can be written inside a
`match` arm; the "value" that arm produces never reaches the rest of the body, because
the function itself is exited. The `Ok` arm, on the other hand, produces an ordinary
value (`value`), and that value is bound to `number`, and the body continues from there.
The two arms' different natures — one exits the function, the other yields a value —
remain exactly the same behind the question mark too, just invisible.

The real payoff of the shortening in the source shows up when a function does more than
one parse. A function that parses three separate fields in turn would need a five-line
`match`, repeated three times by hand (fifteen lines in total); the same function drops
to three lines with the question mark — a single mark after each field. The two runs
measured in this lesson (a one-line shorthand, a five-line hand-written form) are the
smallest example of this difference; as the number of fields grows, the difference in the
source grows too, but the `after-question-success` / `after-question-failure` pair both
forms produce stays the same. The shorthand's growth is not linear — while the number of
source lines drops, the run itself never changes.

The question mark works the same way on `Option` too: if the function it is inside
returns `Option`, it unwraps the value inside `Some`, and in the `None` case it returns
`None` from the function immediately. This lesson does not exercise that second
use — the `Option` methods measured in the Expressing Absence with a Type lesson were
built around producing a value (`unwrap_or`, `unwrap_or_else`), while what is measured
here is early return. Even though both uses carry the same mark, one produces a value
(the way `unwrap_or_else` does), the other exits the function; what this lesson measures
is only its form on `Result`, the one that exits the function.

#### A Limit on the Measurement

In the run above, `after-question-success` and `after-question-failure` always move
together: when one is one, the other is zero, and when one is zero, the other is one.
This is not the result of a single observation, but of the state of the same line across
two different calls — both actually represent the same `record(ledger, label)` call, only
the `label` argument changes with the caller.

**EH9.** Early return silently skips the body that follows it — it prints no warning,
leaves no mark. In the call made with the `"twenty-one"` input, the `record` call never
runs, so no record is dropped into the counter ledger; this leaves no trace saying "a
step was skipped," it only shows up through the *absence* of the expected record. If the
skipped step itself had produced a side effect — writing to a file, incrementing a
counter, printing a log line — that side effect would not have happened either. This
lesson shows this exactly through the `record` call itself: `record` is the skipped
step's *only* side effect, and because that side effect does not happen, the count stays
at zero.

This is a different situation from the previous lesson's `Err` arm being written in the
source but not run. There, both arms stood explicitly inside the same `match`, and both
were visible. Here, the "skipped body" is not written as a separate arm — every line
after the question mark is implicitly skipped in the error case. Someone reading the
source can see under what condition the lines after `?` will run, but does not see in the
source that the compiler produces a separate arm for this; that arm only surfaces in the
run, when measured with a counter.

This silence is the result of a design decision, not a flaw. The question mark prints no
warning, because the error itself is already carried as a return value — as long as the
caller checks that value, the "why it was skipped" information is not lost, it just lives
in the return value rather than in the rest of the body. This lesson does not track that
information; it only counts whether the rest of the body ran. If the code calling
`resolve` does not check the returned `Result`, it loses both that information and the
chance to see what was skipped — but that is the result of the caller's decision not to
check, not of the question mark.

The same silent skipping repeats in the same way through a chain of question marks that
crosses function boundaries in succession. If a function calls another function
internally with a question mark, and that function in turn calls a third function with a
question mark, the failure at the bottom rises all the way to the top by silently
skipping all three bodies — none of them leaving a trace. This lesson does not exercise
this multi-layered chain; it only shows, at a single boundary, that the skipping itself
is silent.

Thinking through this three-layer chain makes the limit even clearer. Say the bottom
function does a parse and fails; the middle layer calling it would have run three more
lines in its own body, but never runs them; the top caller would have run five more
lines, and it does not run them either. In total, an eight-line body is silently skipped
because of a single failure — but nothing in the source, no comment, no special syntax,
marks these eight lines as "skippable." Only the fact that all three layers return
`Result` and that each layer's call is followed by a question mark shows that this
silent skipping is possible.

The distinction measured in this topic's first two lessons still holds here: the
difference between eager and lazy bodies in `Option`, and between the success and
failure arms in `Result`, stands in the same way here too — only now the `match` arms
are no longer written by hand, they are hidden behind a single mark. The shorthand does
not eliminate any of the three classes measured (every time, only when needed, never); it
only shrinks the source that carries those classes.

#### Summary

- The question mark reduces the early return written after a `Result` call to a single
  mark; if it returns `Ok`, it unwraps the value and the body continues, if it returns
  `Err`, the function returns immediately.
- The body after the question mark runs **only when needed**, when the call
  succeeds — it runs **never** when the call fails.
- The same function, built with a hand-written `match` and an early `return`, gives **the
  same counts**: the shorthand shortens the source, not the run — and this difference
  grows as a function does more than one parse.
- Early return silently skips the remaining body; the skipped step's side effect does not
  happen at all either — no warning, no partial record.
- The skipped body does not appear as a separate arm in the source; it is noticed only
  through the missing record, when measured with a counter.
- The question mark automatically converts different error types with `From`; who calls
  this conversion is the subject of the next lesson.

#### Next Step

The `NumberError` above carried a `From` implementation converting
`std::num::ParseIntError` into its own type — how the question mark calls this
conversion was not shown in this lesson, only used. There is no explicit line in the
`resolve` function's body calling this conversion; the `text.parse()?` form itself both
checks the error and converts it when needed. The Custom Error Types lesson measures who
calls this conversion: does the shorthand make this call without the caller writing
anything, or is a hand-written conversion needed — and can both approaches carry the
same information, or does the implicit conversion lose something?

### Custom Error Types

Source: https://academia.sh/en/courses/rust-abstraction/custom-error-types

Measuring that the question mark converts one error type into another without the caller writing any conversion call, and that this implicit conversion — unlike a hand-written one — cannot carry context information from the caller.

The Shorthand for Error Propagation lesson showed that the question mark converts
different error types into each other, but it did not go into *how* that conversion
happens — it only used it. This lesson opens up that conversion: when an error moves
from one type to another, who calls that transition, and is that call visible in the
caller's source?

The `impl From<std::num::ParseIntError> for NumberError` block set up in the previous
lesson is the piece that unlocks this question. That lesson used this block only as a
precondition needed for the question mark to work; this lesson examines that same block
*itself* — what is built inside it, what is discarded, and who triggers that
construction, how many times.

The Interfaces, Errors and Generics course measured error wrapping and the error
chain — whether an error carries another error inside it, how an error's identity is
preserved through a chain. This lesson measures not wrapping but **conversion**: an
error type moves forward not by keeping its own form, but by being converted entirely
into another type. Rust keeps these two mechanisms separate — wrapping was that course's
subject, conversion is this lesson's.

Wrapping puts an error inside an envelope; inside that envelope, the original error stays
intact and can be taken back out when needed. Conversion, by contrast, destroys the
original error: the source type is given up, a new type takes its place, and that new
type can no longer turn back into the old one — unless it stored the old one inside
itself as a field. This lesson measures this second mechanism with an example where
`NumberError` does not store `ParseIntError` inside itself, it only reduces it to a
signal. The `From` implementation shows where this reduction happens: the source error's
data is either kept or discarded while the target type is being built — that is the
decision of whoever writes the implementation.

This topic's first three lessons measured how many times `Option`'s and `Result`'s own
success and failure arms ran; the error values inside those arms did not matter *in
themselves*, `NumberError` was an empty struct carrying no information. This lesson
fills that emptiness: `NumberError` now carries a `field`, and whether that field is
filled correctly or not depends on where and how the conversion is done.

The word "custom" in the lesson title points to an error type designed to be specific to
its source: `NumberError` is not part of the standard library, it is a struct built to
carry this program's own form of failure. In a real program, such a type could also be an
enum instead of a single field — each variant could carry a different kind of failure (a
field missing entirely, a number out of range, a format mismatch). This lesson does not
build that variety; with a single struct and a single field, it focuses on the question
of who calls the conversion. Each variant of a diversified error type could use the same
`From` mechanism too; the axis being measured does not change.

#### Who Calls It: Implicit Conversion

If an error type can be produced from another error type through a `From`
implementation, the question mark calls that production automatically. There is no line
in the caller's source showing that call — `?` is the only character standing there.

The run below stands in for a small chain that parses a three-field record: the `age`,
`score`, and `attempt` fields are each checked in turn by the `link` function. The chain
itself is linear — one field does not wait for another, all are called
independently — but the word "chain" here describes the sum of three separate links: how
many links the conversion actually ran in is read from the total of the three.

```rust
// conversion.rs — the NumberError/From slice of times.rs: how many links the conversion ran in
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

struct NumberError {
    field: String,
}

impl From<std::num::ParseIntError> for NumberError {
    fn from(_: std::num::ParseIntError) -> NumberError {
        NumberError { field: String::from("unknown") }
    }
}

fn link(_field: &str, text: &str) -> Result<i32, NumberError> {
    let number: i32 = text.parse()?;
    Ok(number)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let entries = [("age", "thirty"), ("score", "78"), ("attempt", "three")];

    for (field, value) in entries {
        match link(field, value) {
            Ok(number) => println!("{:<8} success: {}", field, number),
            Err(error) => {
                record(&ledger, "error-conversion");
                println!("{:<8} failure, recorded field: {}", field, error.field);
            }
        }
    }
    println!();
    println!("links in the chain: {}", entries.len());
    println!("links where conversion ran: {} times", times(&ledger, "error-conversion"));
}
```

```
age      failure, recorded field: unknown
score    success: 78
attempt  failure, recorded field: unknown

links in the chain: 3
links where conversion ran: 2 times
```

**EH10.** No line in `link`'s body other than `text.parse()?` mentions
`NumberError` — the `ParseIntError` that `parse` produces automatically converts into
`link`'s return type, `NumberError`, because an `impl From<ParseIntError>` is written for
`NumberError`. Two of the three links in `entries` (`"age"` and `"attempt"`) cannot be
parsed, and `error-conversion` increases twice; because `"score"` can be parsed,
conversion never runs in that link. While the number of links in the chain stays fixed
(three), how many links the conversion actually ran in depends on the input — here, two
out of three.

**EH11.** Both printed `field` values are `"unknown"` — even though the fields that
actually failed are `"age"` and `"attempt"`, the error value itself does not know this.
The `From` implementation's signature is fixed: it only takes a `ParseIntError`, it does
not know which `field` argument `link` was called with. The conversion really does
run — the `error-conversion` counter proves it — but the caller (the `?` inside `link`)
cannot pass any context to this conversion, because what calls the conversion is not
`link` itself but the code the compiler generates for `?`.

The only place in `link`'s body that mentions `NumberError` is the return type
(`Result<i32, NumberError>`); the body itself only writes `text.parse()?`. This is the
same benefit of the shorthand measured in the previous lesson — the source stays
short — but its cost shows up here too: as the source shortens, the information that
source *can call on* narrows as well. Whoever calls `link` knows the `field` (the first
element in `entries`), but `link`'s own body carries no way to pass that information on
to `NumberError`, because the passing happens behind `?`, outside `link`'s control.

Both increases in `error-conversion` fall into the "every time" class: for every field
with invalid input, the conversion runs unavoidably — not conditionally, not
probabilistically, if the input is invalid the conversion runs with certainty. This is
different from the "only when needed" class seen with things like `lazy-default-needed`
or `after-question-success` in previous lessons: there, the count could be zero or one
depending on the input; here, for every failed link, the conversion runs exactly once,
unconditionally.

The three elements in the `entries` array are processed in the same order on every
repetition of the run; changing that order does not change the `error-conversion` count,
because each link is evaluated independently — an earlier link succeeding or failing has
no effect on the next link's result. This continues the same pattern introduced as a
validation pipeline in the previous lesson: here it additionally counts whether each link
ran a conversion too. The resulting count of "2 times" says, together, exactly how many
of the three links failed and that the conversion was actually triggered in each of
those failures.

#### A Limit on the Measurement

Every error conversion can lose information. If the converted-to type does not carry the
source, the lost information cannot be brought back unless it is carried by hand. This is
not a specific shortcoming of `NumberError`, it is a natural consequence of the `From`
signature: `fn from(value: Source) -> Target` only takes the value of type `Source`, it
sees no variable from around where it was called. This limit is the same for every
`From` implementation — it only becomes visible for types like `NumberError` that need
information *outside* the source error (such as the `field` name). The run below repeats
the same scenario, writing the conversion by hand.

```rust
// manual_conversion.rs — the caller writes the conversion explicitly, losing no context
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

struct NumberError {
    field: String,
}

fn link_by_hand(field: &str, text: &str) -> Result<i32, NumberError> {
    text
        .parse::<i32>()
        .map_err(|_| NumberError { field: field.to_string() })
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let entries = [("age", "thirty"), ("score", "78"), ("attempt", "three")];

    for (field, value) in entries {
        match link_by_hand(field, value) {
            Ok(number) => println!("{:<8} success: {}", field, number),
            Err(error) => {
                record(&ledger, "error-conversion");
                println!("{:<8} failure, recorded field: {}", field, error.field);
            }
        }
    }
    println!();
    println!("links in the chain: {}", entries.len());
    println!("links where conversion ran: {} times", times(&ledger, "error-conversion"));
}
```

```
age      failure, recorded field: age
score    success: 78
attempt  failure, recorded field: attempt

links in the chain: 3
links where conversion ran: 2 times
```

**EH12.** The counts are exactly the same: conversion runs in two of the three links. But
the `field` values are now correct — when `"age"` fails, the error prints `"age"`; when
`"attempt"` fails, the error prints `"attempt"`. The difference is that the closure given
to `map_err` can *capture* the `field` parameter: the `field` variable in
`link_by_hand`'s own scope is reachable from inside the closure. `From::from` does not
allow such a capture — its signature only takes the source error, it takes nothing from
the calling context. This is concrete proof of why the shorthand (`?`) sometimes falls
short: the shorthand is fast and short, but it can only carry as much information as the
target type can produce from the source error; if it needs to carry context *outside*
the source error (here, the `field` name), a hand-written conversion is needed.

This limit points to a general rule: a conversion is irreversible if the target type does
not hold the source or its context. `NumberError` having a `field` makes this loss
visible — if it had been an empty struct (as in the previous two lessons), there would
have been nothing to lose, because there was no information to carry in the first place.
When an error type is designed to carry information, this design decision comes along
with it: an implicit conversion, or a hand-written one that preserves context?

The difference between `link` and `link_by_hand` is a variation on the "same count,
different source length" example seen in the previous two lessons. There, the shorthand
and the hand-written form gave the same count and the difference was only in line count;
here too the count stays the same, but the difference is no longer in the line count, it
is in the *information the error carries*. The brevity the shorthand wins this time comes
with a cost — and that cost becomes visible not through the run counter, but through the
printed `field` value.

The count alone is not a sufficient criterion when choosing between these two
designs — both runs gave the same "links where conversion ran: 2 times" line. The
decision lies between how short the source will stay and how much information the error
will carry. `link`, with its one-line body, represents the shortest form, but its error
always says `"unknown"`. `link_by_hand` carries a three-line `map_err` call, but its
error names the correct field. A codebase can contain both forms: if which field an
error came from does not matter (if just "something failed" is enough), the shorthand can
be preferred; if the error's source will be shown to a user or a log record, a
hand-written conversion is needed.

The same implicit/manual distinction also raises a question about the conversion's
*cost* — but this lesson does not measure that cost. `From::from`'s body here amounts to
building a single struct; `map_err`'s closure does the same job. How many times each of
them ran was measured above (two), and each body is the same size. The only difference is
in *what* gets built: one builds a fixed string, the other a string coming from the
calling context.

The Trait Definition and Implementation lesson measured that a trait's default body
might never run on one type and might run on another — two different implementations of
the same contract could give two different run counts. `From<ParseIntError> for
NumberError` is a trait implementation too, and this lesson carries the same principle:
the implementation is written once in the source, but in a run it can be called zero or
more times depending on the input. The difference is that this implementation's call
never appears explicitly in the source — it stays behind `?`.

#### Summary

- If an error type can be produced from another error type with `From`, the question
  mark calls that production automatically; no line in the caller's source shows that
  call.
- Which links in a chain the conversion actually ran in can be counted; here the
  conversion runs **every time** in two of the three links — unavoidable for every
  failed link, not conditional.
- `From::from`'s signature only takes the source error; it can take no information from
  the calling context (such as which field failed).
- A hand-written conversion (a closure with `map_err`) can preserve this information
  because it can capture variables from the calling scope — it gives the same count
  while carrying more information.
- Every error conversion can lose information; if the target type does not carry the
  source or its context, this loss cannot be reversed.
- The choice is between the source's brevity and the richness of the information the
  error carries; whichever is chosen, the count of how many times the conversion runs
  stays the same.

#### Next Step

This topic's first four lessons measured the situations where an operation *might*
fail — `Option`'s absence, `Result`'s arms, the shorthand's silent skipping, the
conversion's loss of information. One thing is common to all of them: the caller took
the failure as a value and decided for itself what to do — it continued, used a
fallback, returned early, carried the information, or lost it. But the decision was
always the caller's.

The Panic and Unrecoverable Errors lesson measures the exact opposite: a design where a
failure, without producing any value, without leaving the caller any choice, stops the
remaining body outright. A function returning `Result` presents its failure as data and
even lets the caller ignore it; the design measured in this topic's last lesson, on the
contrary, models a stop that cannot be ignored — and it compares how the remaining-body
counts the two designs give for the same failure diverge.

### Panic and Unrecoverable Errors

Source: https://academia.sh/en/courses/rust-abstraction/panic-and-unrecoverable-errors

Modeling side by side a design where a failure stops the remaining body without leaving the caller any choice, and a design where the same failure is met with a return value, and comparing the counts the two designs give for the remaining body.

The Go Fundamentals course actually triggered a panic and caught it with a recovery
call, printing a result in the form `panic=true` / `panic=false` — the panic made a
stack trace visible, while the recovery itself made only a flag visible. That course
could use this catching mechanism because there the panic was triggered at the boundary
of a single call, in a controlled way. This lesson measures the same distinction without
ever triggering it: a real panic is neither set up here nor caught with a recovery call.
Panic text is environment-dependent data — the file name, line number, and the shape of
the call stack vary with the machine it runs on — and this lesson does not print such
text.

Instead, the distinction is **modeled**: the same failure is built once in a design met
with `Result`, and once in a design written in the form "nothing after this point runs,"
and the counts the two designs give for the remaining body are compared. The Custom
Error Types lesson showed that a failure can be carried as a value, and that the caller
decides for itself what to do with that value. This lesson measures a design where that
decision is *refused*: a failure that leaves the caller no choice.

Every mechanism measured in this topic's previous four lessons — absence, failure arms,
the shorthand, conversion — shared one common assumption: failure was a *value*, and the
program kept running along with that value. This last lesson looks for the limit of that
assumption: can every failure be handled this way, or is the "continue" option itself
meaningless for some situations? An unrecoverable situation is, by definition, one where
an assumption too fundamental to return as a `Result` and leave to the caller has
collapsed — and instead of separating these two kinds from each other, this lesson
counts the effect both have on the *remaining body*.

#### Two Designs, the Same Condition

The two words in the lesson title — panic and unrecoverable error — are two names for
the same idea. "Panic" is how Rust's runtime names this state; "unrecoverable error" is
the same state's conceptual name, not specific to Rust alone. This lesson treats both
under the same measurement: a failure stopping the remaining work without leaving the
caller the option to continue.

The two runs below handle the same invalid input (`-5`) with two different designs. In
the first, the failure is met with a `Result`, and the caller continues its remaining
work even after seeing the error.

```rust
// recoverable.rs — the error is a value, the caller chooses to continue
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn validate(input: i32) -> Result<i32, ()> {
    if input < 0 {
        return Err(());
    }
    Ok(input)
}

fn remaining_work(ledger: &Ledger) {
    record(ledger, "recoverable-continue");
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    match validate(-5) {
        Ok(value) => println!("valid value: {}", value),
        Err(()) => println!("invalid input met, program continues"),
    }
    remaining_work(&ledger);

    println!();
    println!("recoverable-continue: {} times", times(&ledger, "recoverable-continue"));
}
```

```
invalid input met, program continues

recoverable-continue: 1 times
```

**EH13.** `validate(-5)` returns an `Err`, but this return does not stop the program:
`match` runs the `Err` arm, then the flow reaches `remaining_work` in the ordinary way
and runs it. `recoverable-continue` climbs to one. The caller had an option — seeing the
error and continuing — and it used that option.

In this design, `validate` reports its failure with exactly the mechanism measured in
this topic's previous four lessons: it returns `Result<i32, ()>`, the `Err` arm carries
no value (`()`), but its presence is enough. `main`'s body meets this value with a
`match`, and in both arms the program reaches the same place, `remaining_work`. Calling
`remaining_work` is not a result of the `match` — it is written *after* the `match`
finishes, as an ordinary next line. This is a difference from the pattern seen in
previous lessons: there, the arms did independent jobs; here, both arms lead to the same
next step, only printing a different message.

The second design handles the same condition without ever offering the caller this
option.

```rust
// unrecoverable.rs — when the condition fails, the remaining body is never called by design
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}
fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn continue_work(ledger: &Ledger) {
    record(ledger, "unrecoverable-continue");
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let input = -5;

    if input >= 0 {
        println!("valid value: {}", input);
        continue_work(&ledger);
    } else {
        println!("unrecoverable condition: the remaining body is never called by design");
    }

    println!("unrecoverable-continue: {} times", times(&ledger, "unrecoverable-continue"));
}
```

```
unrecoverable condition: the remaining body is never called by design
unrecoverable-continue: 0 times
```

**EH14.** The same condition (`input < 0`) this time results in `continue_work` never
being called. `unrecoverable-continue` stays at zero — written in the source, but
running **never** in this run. Putting the two runs side by side makes the difference
clear: the same invalid input leads to the remaining work running in one design and
never running in the other. The count comes from the design itself — not from how
"invalid" the input is.

This second design has no `Result` at all — `main` itself decides with `if`/`else` and
calls `continue_work` only in the valid branch. The `else` arm itself prints a message,
but this message is not an error *value*, only an informational note; the program itself
ends normally after this message. This lesson presents this second design as a *model*
of a real unrecoverable situation — a real unrecoverable situation typically stops the
program without even waiting for `main` to finish; here `main` completes normally, only
the `continue_work` call ends up skipped. The model carries the "remaining work does not
run" outcome; it does not carry when and how the program itself terminates.

Neither of these two programs shows a real panic; both are written with ordinary
`if`/`match` branching, and both terminate normally. The model carries not the panic
state's *actual mechanism* (unwinding the call stack) but its *observable outcome* (the
remaining body never running). A real panic produces this "never runs" outcome
automatically, in a single call, without stopping every function in the intermediate
layers one by one — this lesson does not build that automation, it only compares its
outcome with two hand-written designs.

Why this modeling is necessary comes from the ban on environment-dependent data. When a
real panic is triggered, the runtime produces a text; that text includes which file and
line it stopped at, and the call stack's shape at that moment. All of this information
depends on the build environment, the file path, and even the compiler's version — the
same source code does not produce the same text when compiled and run on a different
machine. The rule kept throughout this course (no printing of real paths, real memory
addresses, version-dependent output) applies here too; this is why the panic state is
not really triggered, and instead only the part that *can be counted* — whether the
remaining body runs — is modeled.

This modeling belongs to the same family as the rule measured in the previous four
lessons that "code the compiler rejects is not placed in a code block." There, the
compiler's rejection was described in prose, not placed in a code block; here, the panic
state itself is never set up at all, and its outcome is modeled instead. What both have
in common is putting a number that can be read from the source in place of
environment-dependent output.

#### A Limit on the Measurement

The difference between the two designs above does not say which one is "correct." Panic
is not a kind of error, it is a design decision — the same failure can be written in
either form, and the choice depends on what is left to the caller.

**EH15.** The recoverable design leaves the caller an option: ignoring the error, using
a fallback, or producing its own error. The design modeled as unrecoverable closes off
that option from the start — the remaining work never runs by design unless the
condition is met, and changing that is not in the caller's hands. A real panic takes this
second design one step further: the stop happens not only in one function's body but in
every layer that calls that function, without any explicit code being written. This
lesson does not exercise this multi-layered stop; it only shows, at a single layer, how
the "never runs" outcome can be modeled.

This difference can be compared to the silent skipping measured in the Shorthand for
Error Propagation lesson, but it is not the same thing. There, early return produced a
`Result`; the remaining body did not run, but the function returned normally, and the
caller could meet that return with a `match`. In this design modeled as unrecoverable,
the function also returns normally — the model is not a real panic — but a real panic
differs at exactly this point: it produces no normal return, it leaves the caller no
value to meet with a `match`. This is the real dividing line between recoverable and
unrecoverable: one leaves a value, the other cuts the flow without leaving any value at
all.

In the design modeled as unrecoverable, `continue_work` was written as a single body,
but in a real program the remaining work could consist of several steps — opening a
record, computing a total, printing a result. This lesson gathered these steps into a
single function, but the multi-step skipping seen in the Shorthand for Error Propagation
lesson applies here too: when the `input >= 0` condition is not met, all the steps
*inside* `continue_work` are skipped together, not one by one. How many separate steps
the remaining work consists of does not matter — as long as the condition is not met,
none of them run, all of them together fall into the "never" class.

The moment a designer has to choose between these two paths is usually decided by the
answer to "can this situation actually happen, and if it does, does the rest of the
program stay trustworthy." A user's input being invalid is an expected situation — the
recoverable design is the natural choice here, because the caller can anticipate this
situation in advance and keep a response ready. By contrast, a situation that breaks a
program's own internal consistency — an array carrying fewer elements than expected, a
setup left incomplete — is usually treated as unrecoverable, because in that situation
the assumptions the rest of the code was written under are no longer valid, and
continuing could lead to new, unpredictable errors.

Drawing a precise line between these two extremes is not this lesson's job — the line
depends on the program's own domain and the designer's decision. This lesson only makes
the *outcome* of that decision countable: whichever design is chosen, whether the
remaining body runs can be verified not by reading the source, but by measuring a run.
The `1`/`0` difference between `recoverable-continue` and `unrecoverable-continue` is the
last example of the measurement carried throughout this topic: how many times a body
written in the source runs in a run.

Whether a design ends up recoverable or unrecoverable does not follow automatically from
the failure's *nature* — it is a decision. The same invalid input can be handled in one
program as "log the error, continue with a default value"; in another program, the same
input can be handled as "this should never have happened, continuing means nothing."
Rust itself does not impose this decision; both `Result` and an ordinary function return
are part of the same language, and the choice belongs to whoever writes the source code.

The `-5` input to the `validate` function above actually represents the "same" failure
in both designs — in both, the source says a negative number is considered invalid. The
difference is not in *detecting* the failure, but in *what happens after* it is
detected. This stands in the same place as the axis carried throughout this topic: how
many times a body written in the source runs in a run. Only this time the body being
measured is not a fallback value or a conversion, but the *rest* of a function's normal
flow — and whether that rest runs is read directly from the design itself.

#### Summary

- The same failure can be written with two designs: in the recoverable design, the
  caller continues the remaining work by its own choice; in the design modeled as
  unrecoverable, the remaining body runs **never**.
- A real panic was never triggered in this lesson; the distinction was modeled with
  ordinary branching, without printing panic text — because panic text carries
  environment-dependent data like the file, line, and call stack.
- In the recoverable design the remaining work ran (`recoverable-continue: 1`), in the
  design modeled as unrecoverable it did not (`unrecoverable-continue: 0`) — the same
  condition, a different count.
- Panic is not a kind of error but a design decision; the choice depends on what is left
  to the caller — on whether a situation is expected or breaks the program's internal
  consistency.
- A real panic produces the "never runs" outcome in a single call, without stopping the
  intermediate layers one by one; this lesson measured not that automation, only its
  observable outcome.
- In the recoverable design, the caller meets a value and decides; in the unrecoverable
  design, no value is left at all, the flow is cut directly.

#### Next Step

The bodies measured throughout this topic's five lessons were always inside a
function's own body — how much of it ran when a function was called: a fallback value,
an arm, a conversion, a remaining body. All five lessons settled into the same three
classes (every time, only when needed, never), only which mechanism fell into these
classes changed. The Closures lesson carries the same question to *when* a body is
called: does a closure run the moment it is defined, or only when someone calls it? The
"how many times did it run" question measured in this topic continues there too, but now
what is measured is no longer a failure — it is a definition itself.

## Closures and Iterators

A body capturing its surroundings and being deferred to later, traversal being reduced to a single contract, chained transformations waiting until they are consumed, and conversion to data formats being done with bodies that run per field.

### Closures

Source: https://academia.sh/en/courses/rust-abstraction/closures

A closure's body does not run on the line where it is defined but at the moment it is called: the same job is written as a direct call, a deferred call, and a closure that is never called, and the run counts are compared; what moving versus capturing by reference does to the source value is also measured.

The previous topic measured that the rest of a body might never run at all: an early
return or an unrecoverable error silently skipped the steps written in the source. That
measurement looked at which branch ran — the body itself was always in the same call. This
lesson takes the same "how many times did it run" question down to a smaller unit: not the
branch, but the way the body is stored. Whether a body runs even once now depends on how it
was stored before being called. A closure is a value that can capture something from its
surroundings and defer a body to later; that definition was established in the Programming
Fundamentals course and is not rewritten here. What this lesson measures is how many times
the same job runs across three storage forms, how many times calling the same closure
repeatedly runs its body, and what capturing does to the source value. All three
measurements use the same run ledger: when a body runs, it writes its own name into this
ledger; when it does not run, it leaves no trace. The count is a record read from the run
itself, not a guess.

#### The Same Job, Three Ways of Being Called

A body can be written in three forms: as an ordinary function that is defined and called
immediately; wrapped in a closure, stored under a name, and called later; or wrapped in a
closure and never called at all. The source text is nearly identical in all three — the
difference lies in the distance between where it is defined and where it is called. For an
ordinary function call this distance is zero. Once a closure is stored, this distance opens
up, and the body becomes callable whenever its name is held — or never called at all. The
distance itself is not what is measured; what is measured is whether the body ends up being
called at all.

In the run below, `direct_body` is not a closure but an ordinary function defined inside
another function; this is deliberate, because a nested function in Rust cannot capture
anything from its surroundings — `ledger` is passed to it explicitly. Closure syntax (`||`),
by contrast, turns a body into a value bound to the names around it; this difference is
noted so the comparison stays fair. What is being compared is not "is it a closure" but the
distance between definition and call.

**CI1.** — the claim that "the same job runs on a direct call, does not run until called on
a deferred closure, and never runs by the end of the run on a closure that is never called"
is confirmed by the run below.

```rust
// closure_body.rs — the same job in three forms: direct call, deferred call, closure never called
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    // direct body: not a closure, an ordinary function defined and called right away
    fn direct_body(ledger: &Ledger) {
        record(ledger, "direct-body");
    }
    direct_body(&ledger);

    // deferred body: stored in a closure, does not yet run on the line where it is defined
    let deferred = || record(&ledger, "deferred-body");
    println!("right after being defined: {}", times(&ledger, "deferred-body"));
    deferred();
    println!("after calling: {}", times(&ledger, "deferred-body"));

    // never-called closure: the same slice as the course's measurement core, never called
    let _closure = || record(&ledger, "uncalled-closure");

    println!("direct body: {}", times(&ledger, "direct-body"));
    println!("uncalled closure: {}", times(&ledger, "uncalled-closure"));
}
```

```
right after being defined: 0
after calling: 1
direct body: 1
uncalled closure: 0
```

The deferred body has not yet run on the line where it is defined: the counter read right
after the closure is defined is zero. Once it is explicitly called, the counter jumps to
one — no matter how many lines, calls, or branches sit between definition and call, the
counter would stay at zero until `deferred` is called. The third body, `uncalled-closure`,
is the exact slice from the course's measurement core: it is stored under the name
`_closure` and is never called anywhere else in the program. This line falls into the
**never** class in the course's binding table — being written in the source does not mean
having run. This measurement is not to be confused with the question the JVM, Concurrency
and Performance course asks — which thread runs a given step: there is only one thread here,
and the only question is how many times the body ran.

#### A Closure's Body Runs As Many Times As It Is Called

The previous measurement looked at whether the same body ran even once. But a closure is
not limited to a single call: the same closure stored under one name can be called
repeatedly, and every call re-runs its body — the previous result is never remembered,
stored, or reused. What sets a closure apart from an ordinary value is that its body is
bound to a name and can be called as many times as wanted. This is a different question
from the gap between "never" and "at least once": a closure may never be called, but the
same closure may also be called many times, and in that second case the counter tracks the
call count exactly.

**CI2.** — the claim that "calling the same closure three times in a row runs its body
three times, not once" is confirmed by the run below, where three separate calls raise the
counter to three.

```rust
// multi_call.rs — a closure whose body only reads a captured value can be called repeatedly
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let label = String::from("example-label");

    let multi_called = || {
        record(&ledger, "multi-called-body");
        label.len()
    };

    let a = multi_called();
    let b = multi_called();
    let c = multi_called();

    println!("lengths after three calls: {a}, {b}, {c}");
    println!("how many times the body ran: {}", times(&ledger, "multi-called-body"));
}
```

```
lengths after three calls: 13, 13, 13
how many times the body ran: 3
```

`multi_called` only reads the `label` it captured — it returns its length, never carrying
the value itself out of its body. This is why the same closure can be called three times,
and each call raises the counter by one: three calls, three runs. Had this body instead
carried the value's ownership out — for instance, returning `label` itself — the first call
would have let the value slip away, and the compiler would have rejected the second call: a
body can no longer hand out a value it no longer holds. This rejection is not put into the
source, because a source that does not compile falls outside the lesson's scope; but the
result stands — a closure that only reads its captured value can be called as often as
wanted, while one that consumes it can only be called once. Both are the same ownership
rule, reflected differently in the call count. These three degrees of callability — as
often as wanted, only by mutating, only once — correspond to the three closure traits in
Rust's standard library; their names and the boundary between them fall outside this
lesson's scope, since reading a contract's boundary is a separate lesson's subject. What is
measured here is only the call count's reflection back onto the source.

#### What Capturing Does to the Source Value

Whether closures run is one half of the question; what they capture is the other. A closure
can take a value from its surroundings in two ways: by borrowing (by reference) or by
moving. This choice is not explicit in the body's source text — the compiler decides for
itself by looking at what the body does with each captured name; the `move` keyword forces
this decision by hand. Moving is the closures' version of the ownership rule built in the
Ownership and the Memory Model course: once a value is moved into a closure, that closure
itself becomes the value's owner, and the value's lifetime becomes bound to the closure's
lifetime — even if its body never runs.

**CI3.** — the claim that "a value captured by moving is dropped together with the closure
even if the closure's body never runs" is confirmed by the run below, where the
`captured-value-dropped` counter stays at zero until the closure is dropped and then jumps
to one, while the body's own counter stays at zero throughout the run.

```rust
// capture_lifetime.rs — a value captured by moving has its lifetime bound to the closure itself
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

struct Tracked {
    ledger: Ledger,
}

impl Drop for Tracked {
    fn drop(&mut self) {
        record(&self.ledger, "captured-value-dropped");
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let tracked = Tracked { ledger: Rc::clone(&ledger) };

    // the closure captures "tracked" by moving it: the name "tracked" cannot be used after this line
    let never_called = move || {
        record(&tracked.ledger, "uncalled-closure-2");
    };
    println!(
        "after the closure is defined, before ever being called: {}",
        times(&ledger, "captured-value-dropped")
    );

    drop(never_called);
    println!("after the closure is dropped: {}", times(&ledger, "captured-value-dropped"));
    println!("its body never ran: {}", times(&ledger, "uncalled-closure-2"));
}
```

```
after the closure is defined, before ever being called: 0
after the closure is dropped: 1
its body never ran: 0
```

The `Tracked` value is moved into the closure with the `move` keyword; after this line the
name `tracked` can no longer be used — the closure has become the value's owner. When the
closure is dropped without ever being called, the moved `Tracked` value ends along with it,
and the drop leaves its mark in the ledger. This is an event entirely separate from the
body's run counter: `uncalled-closure-2` stays at zero for the whole run, while the counter
for the captured value's drop jumps to one. The closure's own definition, and its holding
onto the moved value, leaves a real effect on the source even though its body is never
called — only this effect is not what the run counter measures. Even without the explicit
`drop` call, the same drop would have happened once the closure's scope ended; the explicit
call here only made the moment observable, and did not change the outcome.

Capturing by reference, unlike moving, keeps the source name alive; the borrowing rules
established in the Ownership and the Memory Model course apply here too, and this lesson
does not rebuild them.

**CI4.** — the claim that "a closure that captures by reference leaves the name it captured
usable both after the closure is defined and after the closure is called" is confirmed by
the run below, where the name `label` is readable in all three lines.

```rust
// capture_by_reference.rs — capturing by reference keeps the source name alive, unlike moving
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let label = String::from("measurement-label");

    let captures_by_reference = || {
        record(&ledger, "reference-captured-body");
        label.len()
    };

    println!("after the closure is defined, label is still readable: {label}");
    let length = captures_by_reference();
    println!("after the call: {}", times(&ledger, "reference-captured-body"));
    println!("label is still reachable after the closure: {label} (length {length})");
}
```

```
after the closure is defined, label is still readable: measurement-label
after the call: 1
label is still reachable after the closure: measurement-label (length 17)
```

`captures_by_reference` is defined without the `move` keyword, and its body only reads
`label`; this is why capturing reduces to a borrow rather than a move. The name `label` can
be read directly both right after the closure is defined and after it is called — a full
contrast with the previous run, where `tracked` became unusable right after being moved.
The same capture syntax (`|| { ... }`) produces two different ownership outcomes depending
on what the body does with the value it captures: one closes off the source name, the other
leaves it open. This lesson does not rebuild these two outcomes, it only measures that both
are independent of the body's run count. Which path gets chosen is read from whether `move`
is written in the source; how many times the body runs is a separate axis, set only by how
many times the closure is called.

#### A Bounding Measurement

The run ledger only counts body calls; a closure being **defined** is also work, but this
work never enters the ledger. When a closure is defined, the values it captures (if any)
are copied or moved right then, and the closure itself, as a value, takes up a place in
memory — real work, but none of it involves a counter call, because what triggers a call is
not the body we wrote but the closure representation the compiler produces. This is why
`uncalled-closure`'s counter being zero does not mean the closure costs nothing; it only
means its body never ran. What it costs to build the closure falls outside the scope of the
number this lesson measures.

This boundary looks small for a single closure but grows once several are built one after
another. If a closure is itself a value, that value can be placed in another structure,
passed as a parameter, or become a link in a chain — and every link takes up a place at the
moment it is built, even if it never runs. This will matter directly in the next topic: once
several closures are chained into a single traversal contract, the contract itself gets
built, but its bodies do not run unbidden. This lesson measured the first link of that
chain — a single closure.

Put together, these four runs sketch the following picture: whether a body runs depends on
how it is stored, how many times it runs depends on the call count, and which value ends
when depends on how it was captured — and none of these three questions determines the
others. A closure can hold a value and drop it without ever being called; it can be called
many times without ever moving any value; or it can consume its captured value and be
limited to a single call. The source can suggest which answer each question will give, but
the exact number is only ever read from the run.

#### Summary

- The same job can be written in three forms: a direct call, a deferred closure, a closure
  that is never called; among closures, only the ones that are called actually run.
- The `uncalled-closure` body from the course's measurement core did **not** run in this run
  at all — being written in the source does not mean having run.
- If the same closure is called several times, its body re-runs on every call (three calls,
  three runs); a closure that only reads its captured value can be called as often as
  wanted, while one that carries the value out of its body can only be called once.
- The lifetime of a value captured by moving is bound to the closure itself; even if the
  body never runs, the captured value is dropped together with the closure.
- Capturing by reference, unlike moving, keeps the source name alive — unlike moving, it
  does not change the value's ownership.
- A closure's own definition is a separate act that does not enter the run ledger; the
  count only ever measures body calls.

#### Next Step

In this lesson, a closure's body was stored on its own and ran or did not run depending on
whether it was called; how many times it was called was reflected directly in the run
count. The next lesson carries the same question from a single closure to a chain: when
many closures join around the same contract, the contract itself — the iterator — decides
when this chain advances. That a built chain never enters the run ledger without a single
call to trigger it will be measured as a direct extension of this lesson's
`uncalled-closure` example.

### The Iterator Trait

Source: https://academia.sh/en/courses/rust-abstraction/the-iterator-trait

Traversing a source is reduced to a single contract — a single call asking for the next value in sequence — and that contract only advances when asked: a chain that is built but never consumed never runs its body at all.

The previous lesson measured when a single closure runs: a stored body did not run at all
unless it was called. This lesson carries the same question from a single closure to an
entire source. An array, a range, a string — traversing each of these looks like a
different operation, but Rust reduces all of them to a single contract: a single call
asking for the next value in sequence. This lesson writes that contract itself, and
measures that traversal is really just this one call repeated, and that a chain built over
a source never enters the run ledger until that call actually arrives. What is measured is
not the source's type or content, but when and how many times the contract's method runs.
This is a natural extension of the closures measurement: there, what determined things was
how a single body was stored; here, it is the contract itself, the thing many closures
bind together. The contract stays fixed while whoever implements it — an array, a range, or
the `Counter` written in this lesson — can change; the measured count is independent of
that choice.

#### Traversal Reduced to a Single Contract

A `for` loop, a transformation chain, a summing call — very different-looking forms in the
source all reduce underneath to the same one method: ask for the next value, or stop. This
method's name and signature are gathered into a single contract; once a type implements
that contract, it becomes traversable. The contract itself says nothing about how the
source is stored or how many elements it has — only how it answers "give me the next one."

The standard library's own sources (a growable array, a range) already implement this
contract, but a counter cannot be placed inside their implementation — there is no way to
directly ask how many times the method was called. This is why the run below implements the
contract on its own small type: `Counter`, a source that holds a single count and produces
values one at a time. The contract itself is identical to the standard library's; the only
difference is that the method writes to the ledger every time it is called.

**CI5.** — the claim that "traversing a three-element source visits its contract
implementation's single method four times" is confirmed by the run below.

```rust
// counter_iteration.rs — measures that traversal reduces to a single next-value call
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

struct Counter {
    current: u32,
    limit: u32,
    ledger: Ledger,
}

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        record(&self.ledger, "next-value-call");
        if self.current < self.limit {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let counter = Counter { current: 0, limit: 3, ledger: Rc::clone(&ledger) };

    let mut total = 0;
    for value in counter {
        total += value;
    }

    println!("total: {}", total);
    println!("next-value call count: {}", times(&ledger, "next-value-call"));
}
```

```
total: 6
next-value call count: 4
```

`Counter` implements the contract's single method: if there is a next value it returns it
and advances its own state by one, otherwise it returns an empty answer. Though the `for`
loop looks like one piece of syntax in the source, in the run this method is called four
times on a three-element `Counter`: three times with a value, once with the "nothing left"
answer. The loop does not stop until it sees this fourth call — it does not count elements
itself, it just keeps asking until the answer is empty. The total coming out to six
(1 + 2 + 3) and the call count coming out to four are two faces of the same run: one shows
the elements' values, the other shows how many times the contract was exercised. This
measurement is not to be confused with the question the JVM, Concurrency and Performance
course asks — which thread runs a given step: there is only one thread here, and the only
question is how many times the contract ran.

The fourth call is nowhere explicit in the source — the `for` loop itself produces it, by
continuing to ask until it sees "no value left." This means the traversing side (the loop)
does not need to know in advance how many elements the source holds: `Counter`'s limit was
set to three, but the loop never read that limit, it only followed the answers. The
contract keeps the source's size to itself.

The contract's single method also does not force the whole source to be traversed — it
only advances as far as it is asked. The same `Counter`, built with a limit of five, is
queried only twice below.

**CI6.** — the claim that "when only the first two values of a five-element source are
requested, the contract's method is called two times, not five" is confirmed by the run
below.

```rust
// partial_consumption.rs — an iterator can be dropped before it is fully consumed
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

struct Counter {
    current: u32,
    limit: u32,
    ledger: Ledger,
}

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        record(&self.ledger, "partial-next-value-call");
        if self.current < self.limit {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let mut counter = Counter { current: 0, limit: 5, ledger: Rc::clone(&ledger) };

    let first = counter.next();
    let second = counter.next();

    println!("first two values: {:?}, {:?}", first, second);
    println!("call count while limit is five: {}", times(&ledger, "partial-next-value-call"));
}
```

```
first two values: Some(1), Some(2)
call count while limit is five: 2
```

`counter` was built able to advance to five values, but it was only queried explicitly
twice, and the remaining three values were never touched — `counter` is here left to go out
of scope without further use. The contract's method waits for the source to be "asked," not to be
"finished": the source may hold five elements, but that count only sets an upper bound, it
does not determine the call count. This observation sets up the next lesson's axis in
advance: a source's length and how many times the body running over it runs are not the
same number.

In this run the method was called explicitly, on two separate lines; in the previous run
the same method was called by a loop. The only difference between the two is who does the
calling: in one it is our own `main`, in the other it is the `for` loop itself. The
contract's method carries the same signature and answers the same way in both cases — who
calls it does not change the contract's behavior, only where the call appears in the
source. This is the full meaning of the contract being "single": no matter which syntax
expresses the traversal, underneath it always comes down to the same method.

This single contract also simplifies converting one form of traversal into another: once a
transformation chain or a search call is written, all of it is built on the same contract,
with no separate traversal rule to learn. Most of these higher-level methods are default
bodies of the kind measured in the Trait Definition and Implementation lesson, the course's
opening lesson — they do not contain a `next` call themselves, they inherit a ready-made
body that calls `next` over and over; what was measured there was that a default body never
runs for a type that overrides it, and what is measured here is the same mechanism's
concrete instance inside the traversal contract. Still, the contract existing does not mean
that writing the traversal in the source and it actually happening in the run are the same
moment — that distinction is the next measurement's subject.

#### A Chain That Is Built But Never Consumed

Defining a transformation over a source is not the same thing as running that
transformation. The previous measurement called the contract's method by hand; this
measurement takes on a form seen far more often in source code — a transformation chain —
built on the same contract. Writing a transformation chain (`.map(...)`, for instance) is a
call that combines a source and a closure and produces a new source; what it produces is
itself a value that implements the contract. A line in the course's measurement core builds
a chain that transforms a four-element source but never consumes it anywhere — its result
is never handed to a loop, collected into a list, or sent to a summing call. Whether a chain
in this state runs at all is an application of lazy evaluation; that concept was established
in the Programming Fundamentals course and is not retold here, only how many times it ran is
counted.

**CI7.** — the claim that "when the same four-element source is built once without ever
being consumed, and once consumed by a summing call, the unconsumed body runs zero times
and the consumed one runs four" is confirmed by the run below.

```rust
// unconsumed_and_consumed.rs — the same source is built once unconsumed, once consumed
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    // unconsumed: the exact slice from the course's measurement core, never consumed
    let _lazy = vec![1, 2, 3, 4].into_iter().map(|x| {
        record(&ledger, "unconsumed-chain");
        x * 2
    });

    // the same source, this time consumed by a summing call
    let total: i32 = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "consumed-equivalent-chain");
            x * 2
        })
        .sum();

    println!("how many times the unconsumed body ran: {}", times(&ledger, "unconsumed-chain"));
    println!(
        "how many times the consumed body ran: {} (total {})",
        times(&ledger, "consumed-equivalent-chain"),
        total
    );
}
```

```
how many times the unconsumed body ran: 0
how many times the consumed body ran: 4 (total 20)
```

The two chains' source text starts out identical: the same four-element source, the same
transformation body. The difference is only at the end: the first is stored under a name
and left as is, the second is handed to a summing call. The `unconsumed-chain` counter
stays at zero for the whole run — this line falls into the **never** class in the course's
binding table. Once the equivalent chain is consumed, the transformation body runs once for
each of the four elements, four times in total. A built chain sitting in the source means
that the contract's "give me the next value" call never arrived; without that call, the
body does not run.

The only difference between these two runs is not in the chain's source text but in what
sits at its end. The `.sum()` call is a consumer that keeps calling the contract's method
until the source runs out; the chain left under the name `_lazy` has no such consumer at
all. Writing a chain's transformation body is only the precondition for that body entering
the run ledger — the sufficient condition is a consumer being attached at the chain's end.

This difference can be noticed just by reading the source: the underscore leading the name
`_lazy` is there to silence Rust's "this name is unused" warning — the same mark seen on
`_closure` in the previous lesson. A chain being built and never used leaves a small trace
in the source too: a binding whose name is never read. In the consumed chain, by contrast,
the result is bound to the name `total` and read in the print call; the name does not go
unused.

#### A Bounding Measurement

A body counter of zero for an unconsumed chain does not mean that chain is free. The `map`
call builds a structure wrapping the source and the transformation closure; this structure
sits in memory under the name `_lazy`, just like a closure that is never called in the
previous lesson. The structure itself is a value and takes up a place — but that placement
falls outside the scope of the "how many times did the body run" question the run ledger
measures. The ledger only counts calls inside the transformation closure; it does not count
the structure the chain occupies at the moment it is built. This lesson does not attempt to
measure that cost, it only records that it exists — the same observation as the previous
lesson's bounding measurement, carried from a single closure to a chain of closures.

The same boundary applies to the partial-consumption measurement on `Counter`. A
five-element source having only two values requested and the remaining three never
requested means those three were never produced — but the cost of building the source
itself was already paid, independent of where this cutoff falls. The measured count
reflects the cost of a later request, not the cost of construction.

Together, these two observations draw the boundaries of the "how many times did it run"
question: the counter says precisely when the contract's method was called, but it says
nothing about when the structure holding that method was built, or how much space it took.
A chain gets built, part of it gets requested, the rest is left unrequested — three separate
moments, three separate questions, and the number this lesson measures answers only one of
them, the moment of the call. A question that wants to measure construction and space
occupied needs a different tool; this course does not write that tool, it only states
plainly which question falls outside this number's scope.

#### Summary

- Traversing a source reduces to repeatedly revisiting a single method of its own
  contract; on a three-element source this method is called four times.
- The contract itself is independent of how the source is stored; it only defines how it
  answers "give me the next one."
- The `unconsumed-chain` body from the course's measurement core did **not** run in this
  run — the equivalent consumed source ran four times.
- Building a chain and the chain advancing are separate events; at the moment of building,
  the contract's method is never visited at all; if only two values of a five-element
  source are requested, the contract's method is also called only twice.
- Lazy evaluation itself was not rebuilt in this lesson; only how many times it ran was
  counted.
- A built but unconsumed chain is not free — that cost is not what the body's run counter
  measures; the counter only measures the moment of the call, not the moment of
  construction.

#### Next Step

In this lesson, the difference between a chain never being consumed and being fully
consumed was measured over a single transformation step: zero against four. But
"consuming" does not carry a single meaning — as the partial-consumption measurement
showed, how much of a source is requested also determines how many times the contract's
method is visited. This lesson saw two extremes: never requested (zero) and fully requested
(four). The next lesson takes the same source and the same transformation body and attaches
different consumers to the end of the chain, and shows that this time it is not the chain's
length but the consumer that determines how many times the body will run — one consumer may
want the whole source, another may stop as soon as it finds the first matching value; both
will start from the same four-element source, but one will land at one of the extremes, the
other somewhere in the middle. Together, these two lessons show that "consuming" actually
spans a range from one end to the other: never, partially, fully.

### Iterator Adapters

Source: https://academia.sh/en/courses/rust-abstraction/iterator-adapters

Two chains starting from the same four-element source have source text that is almost identical, yet one runs its body on every element while the other only as much as needed: what determines the count is not the chain's length but the consumer bound to its end, and chained adapters' bodies run in per-element order.

The previous lesson looked at the two ends between a chain never being consumed and being
consumed entirely: zero and four. But these two ends did not exhaust every possibility
"being consumed" can carry — there was a middle ground where a source could be consumed
partially, without being either fully wanted or not wanted at all. This lesson takes the
same source and the same transformation body and attaches different consumers to the end of
the chain, and shows that the resulting count comes not from the chain's length but from
how much the consumer wants. The second measurement then moves from a single adapter to
two: once two transformations are chained, do the bodies run in one order or another — an
order that is not written explicitly in the source and can only be read from the run. This
topic's name recalls calls like `map`, called "adapters"; this lesson shows by counting why
that name fits — an adapter wraps a source without changing it, turning it into a new source
that carries the same contract.

#### Same Source, Different Consumer, Different Count

A transformation chain's source text does not say how many times it will run until it is
told which consumer is bound to the end of the chain. This is a natural combination of the
previous two lessons' measurements: a closure's body did not run until it was called, a
traversal contract did not advance until it was asked — this lesson combines these two
observations to show that "how much is asked" is set by a single call at the chain's end,
the consumer. Two chains built with the same four-element source and the same
transformation body can run different numbers of times for no reason other than that their
ends differ:
one is bound to a summing call that wants the whole source, the other to a search call that
stops once it finds the first matching value. The transformation step in the middle of the
chain does not know, while it is being written, which consumer will call it — this only
becomes clear once the chain is completed and run.

**CI8.** — the claim that "of two chains built from the same four-element source, the body
bound to a summing call runs four times, and the one bound to a call that stops at the
first matching value runs two times" is confirmed by the run below.

```rust
// consumed_and_short_circuiting.rs — the course's measurement core: same source, different ends
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    // consumed chain: on every element
    let total: i32 = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "consumed-chain");
            x * 2
        })
        .sum();

    // short-circuiting chain: only as much as needed
    let found = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "short-circuiting-chain");
            x * 2
        })
        .find(|x| *x > 2);

    println!(
        "how many times the consumed chain's body ran: {} (total {})",
        times(&ledger, "consumed-chain"),
        total
    );
    println!(
        "how many times the short-circuiting chain's body ran: {} (found {:?})",
        times(&ledger, "short-circuiting-chain"),
        found
    );
}
```

```
how many times the consumed chain's body ran: 4 (total 20)
how many times the short-circuiting chain's body ran: 2 (found Some(4))
```

The first line of the two chains is identically shaped: the same four-element source, the
same `x * 2` transformation. `.sum()` wants the whole source, so the transformation body
runs once for each of the four elements — this line falls into the **every time** class in
the course's binding table: as long as the consumer wants the whole source, the body
inevitably runs on every element. `.find(|x| *x > 2)`, on the other hand, looks for the
first matching value: while the source produces 2, 4, 6, 8 in order, the search stops on
the second element (`4 > 2`), and the third and fourth are never touched. This line falls
into the **only when needed** class — all four elements of the source could have been
produced, but the consumer chose to stop after the second value; the body ran only as much
as the consumer asked for. The source's length was four in both cases; how many bodies ran
came out different in each. This measurement is not to be confused with the question the
JVM, Concurrency and Performance course asks — which thread runs a given step: there is only
one thread here, and the only question is how many times the body ran.

The only difference in the two chains' source text is the consumer's name — `sum` or
`find`. The transformation body itself does not know, and does not need to know, which
consumer will call it: the contract's method runs exactly as much as it is asked, and never
sees the rest. Even if a third or fourth transformation step were added to the chain, this
measurement would not change — what decides is not the number of steps but how many times
the consumer says "give me the next one." Left unchanged in the source, the same
transformation body can produce four, two, one, or, as seen in the previous lesson, any
count from zero to four, purely by swapping the single word at the chain's end (`sum` for
`find`, `find` for some other search or counting call).

**CI9.** — the claim that "the same transformation body runs only once when the first
element's transformed value already matches what is sought" is confirmed by the run below.

```rust
// three_consumers.rs — the same body produces a third count with a third consumer
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let found = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "chain-stopped-at-first-element");
            x * 2
        })
        .any(|x| x == 2);

    println!(
        "how many times the chain that stopped at the first element ran: {} (found: {})",
        times(&ledger, "chain-stopped-at-first-element"),
        found
    );
}
```

```
how many times the chain that stopped at the first element ran: 1 (found: true)
```

`.any(|x| x == 2)` sees that the first transformed value (`1 * 2 = 2`) already matches what
it is looking for and stops without ever touching the second element. The same
transformation body ran once with this third consumer — neither the four of
`consumed-chain` nor the two of `short-circuiting-chain`. Three runs together — one, two,
four, plus zero from the previous lesson — show that the same body can produce four
different counts purely by changing the consumer; the count is a property of the consumer,
not of the body. Had the sought value matched only on the last element instead of the
first, `.any` would have traversed the whole source just like `.find` — where the stopping
point falls is affected not only by the consumer's definition but also by where in the
source the condition happens to be met.

#### Chained Adapters Run in Per-Element Order

When two transformation steps are chained one after another, two orderings seem possible:
either the first step processes the whole source and produces an intermediate result, and
then the second step processes all of that intermediate result (per-adapter, in bulk); or
each element passes through the first step and then immediately through the second, before
moving on to the next element (per-element, in sequence). These two possibilities cannot be
told apart from the source text — `.map(a).map(b)` reads consistently with either one; only
the run tells which one actually happens.

The contract established earlier still holds here: the chain has a single "give me the next
one" method, and when this method is called from the outermost step, the call propagates
inward through the chain. When the outermost step wants its own next value, it must first
ask the step before it for a value; that step asks the one before it in turn — the request
propagates backward through the chain and returns with a value from the source. This
propagation happens one element at a time, not for the whole source at once — which already
points to the second possibility, per-element order.

Each `map` call, on its own, produces a new value that implements the source's contract — an
iterator wrapping another iterator, carrying the same contract. The second `map` call takes
the value the first `map` produced as its source, and itself produces a third value carrying
the same contract. When the outermost consumer asks for the next value, it is really calling
the outermost of these three nested contracts; that one asks its own source (the value the
second `map` produced) for a next value, which in turn asks its own source. This nesting can
be observed directly through the trace kept in the run below.

**CI10.** — the claim that "when two adapters are chained, the second step processes a given
element right after the first step does, before the first step moves on to the next
element" is confirmed by the ordered trace kept in the run below.

```rust
// adapter_order.rs — two chained adapters run in per-element order
use std::cell::RefCell;
use std::rc::Rc;

type Trace = Rc<RefCell<Vec<String>>>;

fn trace(log: &Trace, text: String) {
    log.borrow_mut().push(text);
}

fn main() {
    let log: Trace = Rc::new(RefCell::new(Vec::new()));
    let trace_a = Rc::clone(&log);
    let trace_b = Rc::clone(&log);

    let result: Vec<i32> = vec![1, 2, 3]
        .into_iter()
        .map(move |x| {
            trace(&trace_a, format!("first-stage-{x}"));
            x + 1
        })
        .map(move |x| {
            trace(&trace_b, format!("second-stage-{x}"));
            x * 2
        })
        .collect();

    println!("result: {:?}", result);
    println!("order: {}", log.borrow().join(", "));
}
```

```
result: [4, 6, 8]
order: first-stage-1, second-stage-2, first-stage-2, second-stage-3, first-stage-3, second-stage-4
```

The trace order clearly shows that this runs per-element in sequence, not per-adapter in
bulk: `first-stage-1` (the first step processing the first element) and `second-stage-2`
(the second step processing that same result right away) come one after the other; the
second element's processing only begins on the third line. If the run had gone per-adapter
in bulk, the trace would have come as three `first-stage-*` lines followed by three
`second-stage-*` lines — the run shows nothing of the kind. Every element makes a single
journey from the start of the chain to its end; the next element does not begin until that
journey is finished.

A shared trace (`Trace`) was used here instead of the `record`/`count` pair, because this
measurement wants order, not a count — not how many times it ran, but in what order. The
two transformation closures share the same trace container via `Rc::clone`, and both can
write to it, because the container carries its own interior mutability. This setup shows
that the shared-ownership rules built in the Ownership and the Memory Model course apply
here too — this lesson does not rebuild those rules, it only uses them to keep a trace.

If a third adapter were added to the chain, the same pattern would hold: every element
would pass through all three steps in sequence, and the third step would also finish before
the fourth element began. The chain's length does not change the ordering, it only sets how
many stops each element makes.

This ordering is also consistent with the short-circuiting behavior measured in the first
section: had a consumer like `.find` been attached to the end of the chain, it would have
stopped before the third element was ever reached — but any element that was reached would
still pass through every step in sequence, start to finish. Short-circuiting bounds *how
many elements* the chain processes; per-element ordering determines how *each* processed
element moves through the chain. Both are different results of the same contract's "give me
the next one" call.

#### A Bounding Measurement

Two adapters processing the same element back to back does not mean the steps are unaware
of each other — the ordering is observable, but showing it required a trace rather than
inferring it from the chain's source text; the source text of two consecutive `map` calls
does not state this order directly. A second boundary shows up in this lesson's first
measurement: both of the two chains could have produced all four values from the source,
but one stopped at two. This stopping decision is not written anywhere in the chain — it
lives in the consumer's own definition. The same transformation body, chained with a
different consumer, would have produced a different count; this lesson showed that this
count comes from the consumer rather than the chain itself, but it does not give a general
rule for when each consumer stops — every consumer carries its own stopping condition.

A third boundary concerns the measuring tool itself: in both runs a recording call was
added to the transformation body, without which how many times the body ran could never
have been observed at all. The recording call itself is not part of the work being
measured — the transformation's actual job is `x * 2`, the recording is only an observation
tool. Adding this tool may have raised the body's own cost, but the count itself (four, two)
would stay the same without the recording call; the measurement observes what it measures
without changing it.

One last boundary reaches across all three lessons of this topic: every number measured
came from a single run. As long as the source's element count, the transformation body, and
the consumer stay fixed, this number does not change from run to run — as long as the
source is a four-element array, the body inside `map` is `x * 2`, and `.find(|x| *x > 2)`
queries in the same order every time, the body will run exactly two times on every run.
This determinism is the same assumption the previous topics' measurements rested on, and it
is not broken here either.

#### Summary

- Two chains built with the same source and the same transformation body can run different
  numbers of times depending only on the consumer at their end: one ran **every time** (on
  all four elements), the other **only when needed** (stopping once it found the first
  match).
- The chain's length stayed fixed (four elements); the number of bodies that ran did not
  stay fixed (four, two, one) — the consumer decides, not the chain itself.
- The transformation body does not know which consumer will call it; the contract's method
  runs exactly as much as it is asked.
- Two chained adapters run in per-element order: an element finishes the first step, then
  immediately the second, before the next element is touched — not the first step for the
  whole source and then the second.
- This ordering is not read directly from the source text; it had to be observed with a
  trace.
- The shared trace container used to keep that trace is an application of the
  shared-ownership rules built in the Ownership and the Memory Model course; those rules
  were not rebuilt here, only used as an observation tool.

#### Next Step

This topic measured when a single closure runs on its own, how a traversal contract reduces
that running to a single method, and how the consumer at a chain's end determines that
count. The three lessons together applied the question "how many times did this body you
wrote run" to increasingly larger units: a single closure, a single contract, a chain of
contracts. The course's closing lesson carries the same question into a concrete data
format: a transformation body applied to a struct's fields, and how many times it runs per
field while the struct is serialized. There, the chain's elements are no longer a source's
entries but a single struct's fields — the element count is fixed and small, and the
consumer is singular (serialization itself) — but the question being counted is the same:
how many times did this body you wrote run. The distinction learned in this topic stays
valid there too: a body being written in the source does not mean it ran, and how many
times it ran does not have to be the same fixed number every time — what decides is who
calls the body, and when.

### Serialization

Source: https://academia.sh/en/courses/rust-abstraction/serialization

How many times a hand-written transformation body applied to a struct's fields runs per field, how that count grows multiplicatively in a nested struct, and how, in the reverse direction — from text back to a struct — one field's failure never runs the following fields' bodies at all; every body measured across the course's fifteen lessons is gathered here into a single table.

The Iterator Adapters lesson measured how many times the consumer at the end of a chain
calls a transformation body in the source — the chain's length stayed fixed while the
number of bodies that ran varied with the consumer. This closing lesson carries the same
question into a concrete data format: how many times per field does a transformation body
applied to a struct's fields run while the struct is serialized. Here, the chain's elements
are no longer a source's entries but a single struct's fields; the consumer, too, is
singular — serialization itself. The transformation is hand-written in this lesson, with no
third-party crate in use; a serialization framework would produce this body on its own, but
here the body stays visible in the source and countable. The question asked throughout the
course does not change here either: how many times did this body you wrote run? This is the
course's fifteenth and closing lesson; its close gathers the bodies measured across all
fourteen lessons into a single table.

#### A Transformation Body That Runs Per Field

Had there been a serialization framework, it would have produced this transformation on its
own, invisibly growing the body as the field count rose. This lesson writes the
transformation by hand: every field passes through the same helper function to become a
string, and this pass drops one entry into the count ledger every time. This slice of the
measurement core carries only the counting ledger; no trait, bound, or error type is used
in this lesson.

```rust
// times.rs — excerpt: the counting ledger.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

pub type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

pub fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

pub fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}
```

A three-field `Point` struct carries a `serialize` method that turns its fields into a
string in order; every field passes through the same `write_field` function, and each pass
is counted. `Line` is a struct carrying two `Point` values; its own `serialize` does not
touch a field of its own, it calls the `serialize` of the two `Point` values it carries.
`Triangle` is a struct carrying three `Line` values; the same pattern goes one level deeper.

```rust
// field_body.rs — measures how many times a body runs per field, shows multiplicative growth in nesting.
mod times;
use times::{times, record, Ledger};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

fn write_field(ledger: &Ledger, name: &str, value: i32) -> String {
    record(ledger, "field-body");
    format!("{}={}", name, value)
}

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

impl Point {
    fn serialize(&self, ledger: &Ledger) -> String {
        let x = write_field(ledger, "x", self.x);
        let y = write_field(ledger, "y", self.y);
        let z = write_field(ledger, "z", self.z);
        format!("{};{};{}", x, y, z)
    }
}

struct Line {
    start: Point,
    end: Point,
}

impl Line {
    fn serialize(&self, ledger: &Ledger) -> String {
        format!("{}|{}", self.start.serialize(ledger), self.end.serialize(ledger))
    }
}

struct Triangle {
    edge1: Line,
    edge2: Line,
    edge3: Line,
}

impl Triangle {
    fn serialize(&self, ledger: &Ledger) -> String {
        format!(
            "{}#{}#{}",
            self.edge1.serialize(ledger),
            self.edge2.serialize(ledger),
            self.edge3.serialize(ledger)
        )
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let before = times(&ledger, "field-body");
    let point = Point { x: 1, y: 2, z: 3 };
    let _ = point.serialize(&ledger);
    let after = times(&ledger, "field-body");
    println!("Point (3 fields): {} body runs", after - before);

    let before = after;
    let line = Line {
        start: Point { x: 0, y: 0, z: 0 },
        end: Point { x: 5, y: 5, z: 5 },
    };
    let _ = line.serialize(&ledger);
    let after = times(&ledger, "field-body");
    println!("Line (2 points, 6 fields): {} body runs", after - before);

    let before = after;
    let triangle = Triangle {
        edge1: Line {
            start: Point { x: 0, y: 0, z: 0 },
            end: Point { x: 1, y: 1, z: 1 },
        },
        edge2: Line {
            start: Point { x: 1, y: 1, z: 1 },
            end: Point { x: 2, y: 2, z: 2 },
        },
        edge3: Line {
            start: Point { x: 2, y: 2, z: 2 },
            end: Point { x: 0, y: 0, z: 0 },
        },
    };
    let _ = triangle.serialize(&ledger);
    let after = times(&ledger, "field-body");
    println!("Triangle (3 lines, 18 fields): {} body runs", after - before);

    println!("total field-body runs: {}", times(&ledger, "field-body"));
}
```

```
Point (3 fields): 3 body runs
Line (2 points, 6 fields): 6 body runs
Triangle (3 lines, 18 fields): 18 body runs
total field-body runs: 27
```

**CI11.** `Point` carries three fields, and its `serialize` drops exactly three
`field-body` entries — one to one. `Line` does not touch a field of its own at all, it only
calls the `serialize` of its two `Point` values; but since each call carries its own three
entries, the total comes to six. `Triangle` carries the same pattern one level deeper: each
of its three `Line` values leaves six entries, for a total of eighteen. The relationship
between field count and body run count is one to one here — but this relationship only
holds for the leaf-level actual fields (`x`, `y`, `z`); `Line` and `Triangle`'s own bodies
never leave a `field-body` entry themselves, they only delegate to the sub-structures they
carry. Nesting grows the count multiplicatively rather than additively: `Triangle`'s three
edges each multiply their six entries.

#### Parsing in Reverse: One Field's Failure Stops the Rest

The same question can also be asked in reverse: how many bodies run per field to turn a
string back into a struct? Parsing, too, is written by hand — every field passes through a
shared `read_field` function; this function records first, then tries to parse the piece of
the string. `Point::deserialize` reads its three fields in order and places a question mark
after each read call — the early return measured in the Shorthand for Error Propagation
lesson holds here as well: if one field fails, the question mark exits the function without
ever running the remaining fields' bodies.

```rust
// parsing.rs — reverse direction: text to struct, body runs per field.
mod times;
use times::{times, record, Ledger};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

struct ParseError;

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn read_field(ledger: &Ledger, name: &str, key: &str, part: &str) -> Result<i32, ParseError> {
    record(ledger, key);
    let pair: Vec<&str> = part.splitn(2, '=').collect();
    if pair.len() != 2 || pair[0] != name {
        return Err(ParseError);
    }
    pair[1].parse::<i32>().map_err(|_| ParseError)
}

impl Point {
    fn deserialize(ledger: &Ledger, prefix: &str, text: &str) -> Result<Point, ParseError> {
        let parts: Vec<&str> = text.split(';').collect();
        if parts.len() != 3 {
            return Err(ParseError);
        }
        let x = read_field(ledger, "x", &format!("{}-x", prefix), parts[0])?;
        let y = read_field(ledger, "y", &format!("{}-y", prefix), parts[1])?;
        let z = read_field(ledger, "z", &format!("{}-z", prefix), parts[2])?;
        Ok(Point { x, y, z })
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));

    let valid = Point::deserialize(&ledger, "valid", "x=1;y=2;z=3");
    if let Ok(p) = &valid {
        println!("valid text parsed: x={} y={} z={}", p.x, p.y, p.z);
    }
    println!(
        "valid-x: {}  valid-y: {}  valid-z: {}",
        times(&ledger, "valid-x"),
        times(&ledger, "valid-y"),
        times(&ledger, "valid-z")
    );

    let invalid = Point::deserialize(&ledger, "invalid", "x=1;y=not;z=3");
    println!("did invalid text succeed: {}", invalid.is_ok());
    println!(
        "invalid-x: {}  invalid-y: {}  invalid-z: {}",
        times(&ledger, "invalid-x"),
        times(&ledger, "invalid-y"),
        times(&ledger, "invalid-z")
    );
}
```

```
valid text parsed: x=1 y=2 z=3
valid-x: 1  valid-y: 1  valid-z: 1
did invalid text succeed: false
invalid-x: 1  invalid-y: 1  invalid-z: 0
```

**CI12.** For the valid string (`x=1;y=2;z=3`), all three fields are read and three separate
counters each go up by one — the forward direction's one-to-one relationship holds here too.
For the invalid string (`x=1;y=not;z=3`), the `x` field is read and succeeds, and the `y`
field is also read but fails to parse as a number — both of their counters go up by one,
because both were at least attempted. The `z` field's counter stays at zero: the question
mark exits the function the moment it catches `y`'s failure, and the body written for `z` is
never touched. This shows that the forward direction's **every time** class does not stay
unconditional in reverse: a field's body becomes dependent on the fields before it
succeeding.

#### A Bounding Measurement

The one-to-one relationship between field count and body run count holds in this lesson
only because every struct used here carries unconditional fields — every field always
exists, always gets transformed. Had the type of absence measured in the Expressing
Absence with a Type lesson been a field itself, that field's transformation body would no
longer run unconditionally: it would run for some values and not for others, and counting
the fields alone would no longer give the body run count. This lesson does not run that
case, it only states under which condition the relationship holds and under which it
breaks.

A second boundary concerns what the count does not measure. The `field-body` counter only
counts how many times the body was called — it does not measure how long the string it
produced is, or how expensive one field's transformation was. A single integer field and a
field carrying a large collection are the same to this counter — both are counted once. This
distinction, carried since the start of the course, holds here too: the number only shows
how many times something ran, not how long it took or what it produced.

#### Closing Table

| Lesson | Body measured | Class | Bounding measurement |
|---|---|---|---|
| Trait Definition and Implementation | Default trait body (`name`) | every time · never | Zero runs does not mean the body is absent from the source |
| Trait Bounds | Run count of the bounded and unbounded body | every time | The cost is at compile time: a separate body per type, unmeasurable by running |
| Associated Types | Associated-type / generic-parameter calls | every time | The difference is not in the run but in what the caller must write |
| Object Safety and Dynamic Dispatch | `static-dispatch` / `dynamic-dispatch` | every time | Same count is not the same cost; how the call is resolved is not measurable |
| Derived Traits | Hand-written / derived equality body | every time (hand-written) · uncountable (derived) | The derived body is not written in the source, its run count cannot be counted |
| Operator Overloading | `operator-body` (`add`) | every time | The call site is invisible in the source; the count is only known from the ledger |
| Expressing Absence with a Type | Eager / lazy default body | every time · only when needed · never | If the body is cheap the two forms give the same result; only the count differs |
| Failure as a Return Value | Success / failure branch | only when needed · never | Covered code and executed code are separate facts |
| The Shorthand for Error Propagation | Body after the question mark | only when needed · never | Early return silently skips the rest; the side effect also never happens |
| Custom Error Types | Error conversion body (`From` / `map_err`) | every time | The implicit conversion cannot access the caller's context; the loss is irreversible |
| Panic and Unrecoverable Errors | Remaining body (recoverable/unrecoverable) | every time · never | Panic is a design decision, not an error type; no panic text was printed |
| Closures | Uncalled closure body | never | A closure's own definition is separate work that never enters the ledger |
| The Iterator Trait | Unconsumed chain's body | never | An unconsumed chain is not free; its cost cannot be measured by the run counter |
| Iterator Adapters | Consumed / short-circuiting chain body | every time · only when needed | Order is not read from the source, only observed with a trace |
| Serialization | Per-field transformation body | every time · never | The relationship holds only when every field is unconditional; a conditional field changes the class |

#### Summary

- A transformation body applied to a struct's fields runs once per field; in a struct with
  n fields, this body runs n times — three for `Point`, six for `Line` (which carries two
  `Point`s), eighteen for `Triangle` (which carries three `Line`s).
- In a nested struct, field count grows multiplicatively (3, 6, 18), and the body's run
  count tracks this growth exactly.
- This body falls into the **every time** class: every field is inevitably transformed
  every time serialization is called.
- In the reverse direction — text to struct — when one field's parsing fails, the question
  mark silently skips the remaining fields' body; these fields never run in that run.
- If a field is not unconditional (if it carries a type of absence), this relationship
  breaks; counting fields no longer gives the body run count.
- Across fifteen lessons, every body measured settled into three classes (every time, only
  when needed, never); the closing table gathers them in one place.

#### Course Wrap-Up

Across fifteen lessons, this course applied a single question to one mechanism after
another: how many times did this body you wrote run? The Traits and Generics
topic asked this question of a behavior separating from its type, of whether a
bound adds anything to the run, of a contract's two different forms, of whether a dispatch
form is resolved at compile time or at run time, of a body being able to run without ever
being written in the source at all, and of a call hidden behind an operator. The Error
Handling topic carried the same question to a fallback value, a success/failure branch, the
branching behind a shorthand, an error conversion, and an unrecoverable state. Closures and
Iterators, and this closing lesson, applied the same question to a definition itself, a
traversal contract, a consumer at the end of a chain, and now to a struct's fields. Of the
sixteen bodies in the measurement core, eight ran every time, three ran only when needed,
and five never ran — this number did not change across the course, only which mechanism
fell into which class changed.

The Concurrency and Asynchronous Rust course does not drop this question, it changes its
subject: instead of how many times a body ran, it will ask what a task needs in order to
make progress — whether it advances on its own, is advanced by a poller, waits for something
else to finish, or does not advance at all. Next to the Object-Oriented Java course's
question of which type made the decision, and the Interfaces, Errors and Generics course's
question of where the bond forms, this course added the question of how many times
something ran; the next course will add to this trio the question of what is needed in
order to make progress.

