---
title: 'Rust Fundamentals'
source: 'https://academia.sh/en/courses/rust-fundamentals'
language: en
updated: '2026-09-15T04:58:45+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Writes programs with primitive types, pattern matching and collections'
    - 'Distinguishes by running it whether a writing is an expression, a statement or an item'
    - 'Models data with structs and enums and takes it apart with patterns'
    - 'Builds module and package layout with visibility rules'
---

# Rust Fundamentals

When reading Rust source, the most useful question is not what a line does but what it produces: the course measures every construct with one question — does this writing produce a value, does it only do work, or does it only declare a name? Variables, primitive types, control flow, structs, enums, pattern matching, collections and module layout all pass through the same frame.

## Language Basics

The language's design goals tied to the trade-off between guarantees and cost, project layout established through the manifest, immutability as the default and the opening shadowing gives it, the value ranges primitive types carry, a body's last line as its return value, and whether conditional and loop forms produce a value.

### Rust's Design Goals

Source: https://academia.sh/en/courses/rust-fundamentals/design-goals

A measurement core of eighteen writings runs from inside the program and places each writing into one of three classes — expression, statement, item; the result is fixed at eight expressions, five statements, five items, and the rest of the course carries this trio forward as a single question: does what is written in the source produce a value?

The Go curriculum closed with five courses, and its last course measured what can be known
about a program itself: whether a piece of information comes from the source, from a tool,
or from the run. This curriculum takes one more step in the same direction and looks directly
at the source. When a line is written in Rust, that line's fate splits three ways: the line
**is evaluated and produces a value**, **is evaluated but produces nothing**, or **is never
evaluated at all, only declares a name**. The one question asked throughout the course is
this: does what you write in the source produce a value?

This question has been asked in two different forms before. The Java Fundamentals course
measured what the compiler **adds** to the source — how something not written in the source
gets completed during compilation. The Go Fundamentals course, by contrast, measured the
counterpart of something **not being written** — whether the language leaves that defined,
rejects it, or leaves it open. This course departs from both: here nothing is missing, and
the matter is what is produced when what is **written** in the source gets evaluated.

This threefold classification should be read as a design goal, not as a list of three
categories to recite from memory: which of the three classes you write the same piece of work
in determines where that line can be used. You can bind an expression to a variable, pass it
to a function, or embed it inside another expression; a statement fits none of these, because
all you have in hand is `()`. The course's later lessons will show, one by one, where this
boundary is useful — and where it turns into a writing mistake.

#### Expression, Statement, Item

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

| Class | Definition |
|---|---|
| expression | Evaluates and produces a value other than `()` |
| statement | Evaluates but produces `()` — or is never evaluated |
| item | Only declares; it is not itself evaluated, and its value arises when used |

The `()` that appears in the middle of this trio is not a gap — the Primitive Types lesson
takes it up separately, but what needs saying already is this: `()` is itself a type, and it
has exactly one value. A statement does not "produce nothing"; it produces the value of this
single-valued type. `if`, `match`, `loop`, and a block are each a value in Rust; a semicolon
**discards** a value; `fn`, `struct`, `enum`, `mod`, and `const` are never evaluated at all —
they only introduce a name to the compiler. Because these three paths exist, this question
can carry an entire course.

The Programming Fundamentals course built the `if` construct with Python; there, `if` was a
statement and produced no value. This concept is not rebuilt here — this one-sentence
reference exists only to show why the three classes form a separate axis in Rust: the same
construct does not fall into the same class in every language.

#### The Measurement Core

The core used throughout the rest of the course is `expression.rs`. It runs eighteen separate
writings inside the same frame, writes each one to an observation log, and prints the total
per class at the end. The observation log itself is an array of tuples — each row holds a
name, a class, and a value together as three separate pieces of text. A tuple is the most
direct way to carry several values of different types as a single unit; the fact that all
three fields here are text is not a requirement, but a choice made so the table can be printed
in one uniform format. The observation log is not part of the language — this should be kept
in mind while reading the code.

**LB1.** The `observe` function and the `log` tuple (a `String`, `String`, `String` triple) are
the core's own design decision, not a mechanism of Rust. What decides which class a writing
falls into is each line's form in the source; the `observe` call only records this decision
into a table so the eighteen lines can be compared within a single run.

```rust
// expression.rs — everything written in the source falls into one of three classes:
// one that produces a value (expression), one that produces () or is never
// evaluated (statement), one that only declares (item).

use std::collections::BTreeMap;
use std::collections::HashSet;

const LIMIT: i32 = 100;

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

enum Color {
    Red,
    Green,
}

mod measure {
    pub fn unit() -> i32 {
        3
    }
}

fn double(x: i32) -> i32 {
    x * 2
}

fn observe(log: &mut Vec<(String, String, String)>, name: &str, class: &str, value: String) {
    log.push((name.to_string(), class.to_string(), value));
}

fn main() {
    let mut log: Vec<(String, String, String)> = Vec::new();

    // --- expression: writings that produce a value ---

    let a = 7;
    let b = if a > 5 { "big" } else { "small" };
    observe(&mut log, "if as a value", "expression", b.to_string());

    let color = if a > 5 { Color::Green } else { Color::Red };
    let name = match color {
        Color::Red => "red",
        Color::Green => "green",
    };
    observe(&mut log, "match as a value", "expression", name.to_string());

    let block = {
        let inner = double(a);
        inner + 1
    };
    observe(&mut log, "block as a value", "expression", block.to_string());

    let mut counter = 0;
    let loop_result = loop {
        counter += 1;
        if counter == 4 {
            break counter * 10;
        }
    };
    observe(&mut log, "loop returns a value via break", "expression", loop_result.to_string());

    let n = Point { x: 1, y: 2 };
    observe(&mut log, "struct syntax produces a value", "expression", format!("x={} y={}", n.x, n.y));

    let v = vec![1, 2, 3, 4];
    observe(&mut log, "the vec macro produces a value", "expression", format!("length={}", v.len()));

    let total: i32 = v.iter().map(|x| double(*x)).sum();
    observe(&mut log, "an iterator chain produces a value", "expression", total.to_string());

    let mut set: HashSet<i32> = HashSet::new();
    let first = set.insert(5);
    let second = set.insert(5);
    observe(&mut log, "set insertion returns a value", "expression", format!("{} then {}", first, second));

    // --- statement: writings that produce () or are never evaluated ---

    let with_semicolon = {
        double(2);
    };
    observe(&mut log, "a block ending in a semicolon", "statement", format!("{:?}", with_semicolon));

    let mut d = 1;
    let assignment = { d = d + 8; };
    observe(&mut log, "the assignment itself", "statement", format!("{:?} (d={})", assignment, d));

    let mut list = Vec::new();
    let push_result = list.push(d);
    observe(&mut log, "pushing onto a vector", "statement", format!("{:?} (length={})", push_result, list.len()));

    let mut iterations = 0;
    let for_result = for _ in 0..3 {
        iterations += 1;
    };
    observe(&mut log, "the for loop", "statement", format!("{:?} (count={})", for_result, iterations));

    let print_result = print!("");
    observe(&mut log, "the value of printing", "statement", format!("{:?}", print_result));

    // --- item: writings that only declare; their values arise when used ---

    observe(&mut log, "fn declaration", "item", format!("declaration; call gave {}", double(3)));
    observe(&mut log, "struct declaration", "item", format!("declaration; instance gave x={}", n.x));
    observe(&mut log, "enum declaration", "item", "declaration; variant gave green".to_string());
    observe(&mut log, "mod declaration", "item", format!("declaration; call inside it gave {}", measure::unit()));
    observe(&mut log, "const declaration", "item", format!("declaration; reading it gave {}", LIMIT));

    // --- report ---

    println!("{:<40}{:<12}{}", "observation", "class", "value");
    for (name, class, value) in &log {
        println!("{:<40}{:<12}{}", name, class, value);
    }

    let mut tally: BTreeMap<&str, i32> = BTreeMap::new();
    for (_, class, _) in &log {
        *tally.entry(class.as_str()).or_insert(0) += 1;
    }
    println!();
    for (class, count) in &tally {
        println!("{:<12}{}", class, count);
    }
    println!("total       {}", log.len());
}
```

```
observation                             class       value
if as a value                           expression  big
match as a value                        expression  green
block as a value                        expression  15
loop returns a value via break          expression  40
struct syntax produces a value          expression  x=1 y=2
the vec macro produces a value          expression  length=4
an iterator chain produces a value      expression  20
set insertion returns a value           expression  true then false
a block ending in a semicolon           statement   ()
the assignment itself                   statement   () (d=9)
pushing onto a vector                   statement   () (length=1)
the for loop                            statement   () (count=3)
the value of printing                   statement   ()
fn declaration                          item        declaration; call gave 6
struct declaration                      item        declaration; instance gave x=1
enum declaration                        item        declaration; variant gave green
mod declaration                         item        declaration; call inside it gave 3
const declaration                       item        declaration; reading it gave 100

expression  8
item        5
statement   5
total       18
```

**LB2.** The run is deterministic: the same source produces the same eighteen lines, in the
same order, with the same class distribution, on every run. No line reads from a file system,
a clock, or a random generator — everything is computed from a fixed source. Eight of the
eighteen writings are expressions, five are statements, and five are items; this count does
not change for the rest of the course — only each lesson takes its own slice of it.

The final block, which computes the total per class, uses an ordered map (`BTreeMap`), not a
hash map (`HashMap`). This choice is not arbitrary: the order in which a hash map's keys are
traversed can vary from run to run, so even if the total for all three classes comes out
correct, the order in which the rows print is not guaranteed. An ordered map's keys always
traverse in the same order — here, alphabetically, `expression`, `item`, `statement` — so the
printed text also stays deterministic. This is the most concrete example of the course's rule
about environment-dependent data: if a collection's order is going to be used as a
presentation device, that order must come from a structure that carries a guarantee. This
distinction will be its own subject in the Ordered and Set Structures lesson; here it only
explains why the core's own output can be safely compared run after run.

#### Reading the Results

What the eight expression lines have in common is that all of them stand on the right side of
a `let` binding and produce a value: `if`/`match` picks a branch and gives that branch's
value, a block gives its own last line, `loop` gives only the value handed to `break`, struct
syntax and the `vec` macro construct a new value, an iterator chain accumulates a total, and
set insertion returns a `bool`. The five statement lines show the exact opposite: a block
closed with a semicolon, an assignment, `Vec::push`, a `for` loop, and an empty print — all
five print `()`, because all five say "do a piece of work," not "give a value." The five item
lines stand on a separate axis: the `fn`, `struct`, `enum`, `mod`, and `const` declarations
themselves never enter the table at all; what enters the table is the value that arises from
**using** these declarations — the `6` that `double(3)` returns, a field of the `Point`
instance, the call to `measure::unit()`, the reading of `LIMIT`. A declaration produces
nothing the moment it is written; it only introduces a name, and that name produces a value
once it is used in a later line.

**LB3.** The `double` item is used in three separate places in the core: once inside `block`
(to produce the value that feeds the `inner + 1` computation), once in the iterator chain to
apply to each element, and once in the "fn declaration" line, called directly so its result is
written to the table. In all three, `double` itself does not change — what changes is the
argument given to each call and the value that call returns. This makes concrete what it means
for an item to be "valueless": the declaration is single, but the number of uses and the value
each use produces are free.

The count is not equal among these three groups, and it is not expected to be: most of the
source is written to produce a value (8), and the rest is written either for a side effect (5)
or to introduce a name (5). The rest of the course will deepen each of these three groups in
turn — `if`, `match`, `loop`, and the full set of loops in the Control Flow lesson, assignment
and binding in the Variables and Immutability lesson, and the share declarations hold in the
source in the Setup and the Package Manager lesson. The only question this lesson leaves open
is where the class is read from.

#### Limiting Measurement: Class Is Read From the Signature

Two rows that sit side by side in the table carry the answer to this question: "set insertion
returns a value" (expression, `true then false`) and "pushing onto a vector" (statement,
`() (length=1)`). The two look like they do the same work — adding an element to a collection.
But one produced a value during the run, the other did not. The difference lies not in the
nature of the work, but in the signature of the method being called.

`HashSet::insert` both changes the set and returns a boolean saying whether the insertion
actually added a new element. In the core, `5` was inserted twice: the first call returns
`true` because it adds a new element to the set, the second call returns `false` because it
tries to insert the already-present `5` again — the `true then false` in the table is these
two calls placed side by side. `Vec::push` never gives this kind of information: because
adding an element to a vector always succeeds, there is no boolean to return, and the
signature gives only `()`.

This shows that a single piece of work called "insertion" gives two separate Rust methods two
separate classes. What determines the class is not the reader's intuition about what the work
is, but the return type the compiler sees: statement if the return type is `()`, expression if
it is any other type. Because this criterion is formal and mechanical, which class a method
falls into can be told in advance without looking at its call, just its signature — a mutator
that returns `Option` or `bool` is always an expression, a mutator that returns only `()` is
always a statement. This distinction will come up once more in the Collections lesson, with
`Vec::pop` returning `Option`.

This also explains why the course stands under the heading of a design goal: class is not a
runtime observation but a signature decision, and a signature can be read from the source
without running anything. A reader can look at `HashSet::insert`'s return type and say, without
running a single instance, that it is an expression. The standard library's own design
determines this classification in advance; what the lesson measures throughout is what
difference this determination makes when reading the source.

#### Summary

- Everything written in the source falls into one of three classes: an expression produces a
  value, a statement produces `()` or is never evaluated, and an item only declares a name
  whose value arises from use.
- `()` is not a gap; it is a single-valued type, and it is what a statement produces.
- The measurement core classifies eighteen writings in a single run: 8 expressions, 5
  statements, 5 items — this count does not change for the rest of the course.
- The same piece of work (insertion) can give two separate methods two separate classes; class
  is read not from the nature of the work but from the method's return type — its signature.

#### Next Step

The three classes are now defined, but for a source to actually run, there is a much smaller,
much more mandatory set than these: the least a program must write. The next lesson asks about
this smallest set — it shows how, when a piece kept in a separate file that does not live in
the source itself changes, the output changes while the source itself never does.

### Setup and the Package Manager

Source: https://academia.sh/en/courses/rust-fundamentals/setup-and-the-package-manager

There are three parts a source is required to write for it to turn into a runnable binary — the entry point, the crate root, the manifest; all three are items, none of them produces a value on its own, but the three together produce a single output, and a change in the manifest can change that output's name without ever touching the source.

The previous lesson defined the three classes and split eighteen writings among them; none of
them alone was sufficient for a program to run, because the question asked was already a
different one. This lesson narrows the question: what, at minimum, must a source text write,
with nothing missing, for it to turn into a running binary? The answer is three parts, and all
three fall into a class familiar from the previous lesson — item. None of them produces a
value on its own, but the three together produce a single output: an executable program.

This question looks smaller than the previous lesson's question, but it is not independent of
it. The previous lesson asked what something written in the source produces when evaluated;
this lesson asks what frame must stand around the source for evaluation to even begin. The
expression, statement, and item distinction is a classification valid only inside Rust source;
this lesson shows where that source begins and its relationship to the data surrounding it.

#### Three Mandatory Items

The three parts required for a source to turn into a runnable binary are: the **entry point**
(the `fn main` marking where the program will start), the **crate root** (the file itself
where the compiler starts reading), and the **manifest** (the data file holding the package's
name and which language edition it compiles with). A standard project layout separates these
as follows:

```text
project/
├── Cargo.toml
└── src/
    └── main.rs

# Cargo.toml contents
[package]
name = "calculator"
version = "0.1.0"
edition = "2021"
```

These three fall into the item class established in the previous lesson: none of them is
evaluated on its own, each only declares something. `fn main` itself is a function declaration
— just like `double` in the previous lesson. The crate root is a file; the file itself is not
"run," only the declarations inside it are introduced to the compiler. The manifest, in turn,
is not even Rust source; it is data in TOML format, and it is read by the package manager.

The separation in the directory tree is not arbitrary: only source files sit under `src/`,
while `Cargo.toml` sits at the project's root, outside `src/`. This layout keeps "source" and
"data about the source" out of the same folder — a tool scanning all source files only needs
`src/`, and reads the manifest with separate logic. Here we use a single-file source and hand
it directly to the compiler, because the package manager is never run in this course. In a
real project, the crate root is `src/main.rs`, and the compiler starts there and reaches other
files through `mod` declarations — that expansion is the subject of the Modules and Crates
lesson; here it is enough to know the root is a single file and compilation starts from there.

**LB4.** `fn main` being "used" differs from a function being used in the previous lesson:
`double` was called from within the program itself, while `main` is called by no line at all —
what calls it is the runtime environment that takes over when the operating system runs the
compiled binary at the start of a process. The declaration (item) is the same declaration, but
the calling side is outside the source.

This does not mean the source is incomplete on its own; the record is complete at the crate
root, only the source cannot decide at what moment that record turns into a value. A source
can also contain other items, such as `double` or `observe` — all of them stand as
declarations, none runs on its own. Only one carries the name `main` and is recognized by the
operating system as the entry point; the others produce a value only if reached from `main`'s
body or from another item `main` calls. This is the project-level counterpart of the previous
lesson's finding that items are valueless in measurement: a source tree can declare as many
items as it likes, but an item unreachable from the entry point never runs.

**LB5.** The manifest is not Rust source at all, so the expression/statement/item
classification does not truly apply to it — that classification is defined only for Rust
source. But it lands functionally in the same place: the manifest itself is not "run" either,
it only supplies data the package manager reads, and it contributes its name to the final
output. The manifest's own format makes this difference visible too: `name = "calculator"` is
a key-value pairing, not a call or a branch — there is no code path to evaluate in TOML, only
data to read.

The whole these three parts form together is called a **package**: the source tree the
manifest points to, together with the manifest itself, forms a single unit of distribution and
compilation. The package manager's name comes from here too — what it manages is not a single
source file, but the whole that source and identity form together. A package's boundary is the
directory in the file system where the manifest is found; every source file under that
directory counts as belonging to the same package.

TOML itself is a data language designed to support this distinction: it contains only
`key = value` lines and section headers opened with square brackets (such as `[package]`).
There is no branching, loop, or function call — a manifest is not "run," only read straight
through as a plain set of records. This stands in direct contrast to Rust source: the source
has expressions and statements to evaluate, the manifest has only data.

The difference between the crate root and the entry point is a similar distinction and should
not be confused. The crate root is a file (where the compiler starts reading); the entry point
is a specific item inside that file (`fn main`, the function where execution will start). In a
small program the two can sit in the same file, but conceptually they are separate: a crate
root can carry helper declarations linked to other files through `mod`, with no `fn main` in
it at all — we saw this in the previous lesson with `mod measure`. What a package needs to be
runnable is not the crate root itself, but `fn main` being found somewhere in the source tree.

The directory tree and the manifest text block shown throughout this lesson are not run and
carry no numeric claim; they only document what a real project looks like. Every example that
runs for the rest of the course will be set up to work directly with the compiler, independent
of the package manager — this is a consequence of setting up a measurement environment that
requires no network access, not a limitation of the language itself.

To see what the entry point does on its own, the source below runs it in its smallest form.

```rust
// our_program.rs — the least a source must write for it to be able to run
fn main() {
    println!("verification-key={}", 7 * 6);
}
```

```
verification-key=42
```

There is a single item in this source: `fn main`. The source itself produces no value — it
tells the compiler "the program's start is here." The value arises when the program is
**run**, in a way similar to how the previous lesson's `double(3)` call's value arose when the
function was **called**. The difference is that the calling side is outside the source.

The print call inside the body itself falls into a class familiar from the previous lesson: a
statement. The previous lesson's table showed exactly this in its "the value of printing" row
— printing returns `()`, and produces no value itself. The only thing that changes here is
that this statement no longer stands alone, but inside the body of an item named `fn main`:
the item itself produces no value, but when the statement in its body runs, it produces a side
effect (a line printed to the screen). The expression, statement, and item classes can nest
this way — an item's body can be made of statements and expressions, but the item itself still
remains an item.

The crate root being the single file the compiler starts reading is a requirement: a file
without `fn main` can be compiled and written to disk (the `measure` function inside `mod` in
the previous lesson did exactly this), but an executable the operating system can run directly
needs an `fn main` somewhere. This is a single fact that will not appear again for the rest of
the course: a source collection with no entry point can be compiled, but it is not runnable.

The separation of these three mandatory parts from one another is not a coincidence; it is
itself a design decision — it stands on the same axis as the course's title. Separating the
source from the source's identity (name, version, language edition) lets the two change
independently: a project's name can change without its logic ever changing, or its logic can
change without its name ever changing. A design that kept both pieces of information in the
same file could not provide this — every tool reading the source would first have to know
where in the source to look for identity information.

#### Limiting Measurement: The Manifest Is Not Inside the Source

The manifest standing separate from the source has a concrete consequence: you can change the
output's **name** without ever changing the source, only by changing a field in the manifest.
In a normal project, the package manager derives the binary's name from the manifest's `name`
field. The measurement below shows the same principle, without running the package manager, directly
with the compiler's output-naming option — the source (`our_program.rs`) never changes, only the target
name changes.

```bash
name_a="calculator"
name_b="calc-tool"
rustc --edition 2021 -o "$name_a" our_program.rs
rustc --edition 2021 -o "$name_b" our_program.rs
echo "output_a: $(./$name_a)"
echo "output_b: $(./$name_b)"
if [ "$name_a" != "$name_b" ]; then
  echo "binary_names_differ: yes"
else
  echo "binary_names_differ: no"
fi
```

```
output_a: verification-key=42
output_b: verification-key=42
binary_names_differ: yes
```

**LB6.** This measurement does not run the package manager — it is not run in any code block
in this lesson either. What is shown is the **principle** of the package manager deriving the
binary's name from a data field: `rustc`'s `-o` option stands in here for the manifest's
`name` field. The same source file was compiled twice, with two different target names, and
both gave the exact same output; the only thing that changed was the binary's name.

This result belongs to the same family as the previous lesson's "class is read from the
signature" finding: there, a method's class was determined by looking at its signature, not
its call site; here, a binary's name is determined by looking at the manifest, not the source.
In both cases the source itself does not carry a piece of information; that information sits
somewhere outside the source, and the source runs unaware of it. The `our_program.rs` file
does not know, in any of its lines, that it will turn into a binary named "calculator" or
"calc-tool."

The `output_a` and `output_b` lines in the measurement being exactly identical is itself an
observation: when the same source is compiled twice with the same compiler options, it
produces two separate binaries, but their behavior never diverges. This is the
compilation-level counterpart of the previous lesson's determinism finding — there it was
shown that the same source produces the same eighteen lines on every run, here it is shown
that the same source produces the same behavior on every compilation. The only thing that
changes is a name left outside the compilation.

This independence is not one-directional. Once compilation is complete, the produced binary
becomes independent of the source as well — even if the source file is deleted, a previously
produced binary keeps working.

```bash
rustc --edition 2021 -o binary_example our_program.rs
mv our_program.rs our_program_backup.rs
echo "running: $(./binary_example)"
if [ -f our_program.rs ]; then
  echo "source_still_same_name: yes"
else
  echo "source_still_same_name: no"
fi
```

```
running: verification-key=42
source_still_same_name: no
```

**LB7.** The binary is the **result** of compilation; it is not the source itself, but the
source's evaluated state at a specific moment. Even if the source file is moved or deleted,
the binary does not know it, because the link was established at compile time, and from that
moment on the binary is a file on its own. This shows the ultimate purpose of the
entry-point–crate-root–manifest trio: the three together produce a single, self-sufficient
output independent of the source. This is the foundation of a situation commonly encountered
in deployment practice: only the compiled binary is moved to a server, the source never goes
there — because the binary can run on its own, this move carries no drawback.

This distinction has a concrete practical value: a source file carrying the same computation
logic can be compiled under the name "calculator" as an internal tool in one context, and
under a completely different name as part of a distributed package in another. Two separate
identities are obtained without the source ever being copied or edited, only by changing the
manifest around it — the measurement above shows exactly this: `our_program.rs` was compiled
under two different names without ever being opened and edited even once.

This distinction has a practical consequence: renaming a project can be done without changing
a single character in the source text. The crate root and the manifest carry two separate
layers of visibility for the same project — one tells what runs, the other tells how the
project is named and which language edition it compiles with. The two layers do not mix: you
can read the source without reading the manifest, read the manifest without reading the
source, and neither assumes anything about the other.

The same distinction also holds in reverse: the same crate root can be part of two separate
projects with two separate manifests. What determines which project a source file belongs to
is not the file's content, but which manifest points to it. As seen in the `our_program.rs`
example, this is a consequence of the source's portability: the file's content stays fixed
while the manifest around it can change, and these two changes are independent of one another.

The other fields the manifest carries (the version and language-edition lines seen in the
example above) follow the same principle: they sit in a code block and never mix into the
source itself. In this lesson, the `--edition 2021` option was given directly to the compiler
because the package manager is never run; in a real project, the package manager reads this
information from the manifest and passes it to the compiler itself. The source file carries
none of this information — it only knows what it does, not what the manifest around it
carries.

This lesson showed that the previous lesson's "item" class is not limited to a single
declaration line. A file itself (the crate root) and a project's identity (the manifest) can
be read by the same logic too: neither produces a value on its own, both only set up a frame,
and the code running inside that frame produces an output. The next lesson descends into this
frame, into the entry point's body, and takes up that most of the first lines written there
bind a value to a name.

#### Summary

- Three parts are mandatory for a source to turn into a runnable binary: the entry point, the
  crate root, the manifest.
- All three are items: none of them produces a value itself, and the three together produce a
  single output — a running program.
- The side that "uses" the entry point is not the source itself, but the operating system's
  moment of starting a process.
- The manifest is not Rust source and stands outside the source; the output's name can be
  changed by changing only the manifest, without ever changing the source.
- The crate root and the manifest are two separate layers unaware of each other: one carries
  what runs, the other carries how the project is named.
- Once produced, a compiled binary becomes independent of the source: even if the source file
  is deleted or moved, a previously produced binary keeps working.

#### Next Step

A body can now be written inside the entry point, but the first line of that body is usually
to establish a name — to bind a value to a name. The next lesson asks which class this binding
itself falls into, and shows by running it the difference between the same name being rebound
three times and being bound once and changed twice — even though both reach the same result.

### Variables and Immutability

Source: https://academia.sh/en/courses/rust-fundamentals/variables-and-immutability

let is a binding statement and the assignment itself produces (); the same name being rebound three times (shadowing) and bound once and changed twice (mut) reach the same numeric result, but only shadowing can change the type along the way — because one sets up a new binding each time, the other writes over an existing binding.

The entry point's body can now be descended into, and most of the first lines of that body
bind a value to a name — a binding. This lesson measures the binding itself from two angles:
whether the binding operation itself is an expression or a statement, and why Rust's two
separate ways of rebinding the same name — shadowing, which sets up a brand-new binding each
time, and `mut`, which writes over an existing binding — behave so differently from one
another while reaching the same result.

The Programming Fundamentals course built the variable definition with Python, and defined
shadowing there too. These concepts are not rebuilt here; this lesson's share is that Rust
sets up a binding as **immutable** by default, and that shadowing opens a door through this
immutability.

This ordering is not arbitrary. The previous lesson established the smallest set required for
a source to run; this lesson descends into that set, into the entry point's body, and asks how
a name binds to a value there. The next lesson will ask what type this bound value can be —
but before asking what a type is, it is necessary to know how a name holds on to a value of
that type, and how tight that hold is. Immutability is the name of that tightness.

#### Binding Is a Statement

The writing `let name = value;` splits into two separate parts. The right side of the equals
sign is an expression — it is evaluated and produces a value. The whole line, the binding
starting with `let` itself, is a statement: it can never be used as a value anywhere. This
distinction looks small, but its consequences are concrete — you cannot pass a binding as an
argument to a function, add it to a list, or embed it inside another expression, because all
you have in hand is `()`.

**LB8.** In Rust, `let` only stands at the body level and cannot be placed inside an
expression; a writing like `(let x = 5)` is not accepted by the compiler. This is the concrete
counterpart of `let` "always being a statement" — it has no exception, it never turns into a
value in any context. One consequence of this is that a reader of the source can track where a
new name is established from a single place: every new binding opens at the start of a line,
with `let`; there is no need to search for a binding hidden in the middle of an expression.

We saw in the previous lesson's `expression.rs` core that the assignment itself falls into the
same class too (the "the assignment itself" row printed `()`). The same fact is measured again
here, with a small example specific to the subject of variables.

```rust
// assignment_as_statement.rs — the assignment itself produces no value
fn main() {
    let mut x = 1;
    println!("x at the start: {}", x);
    let assignment_result = { x = 9; };
    println!("x now: {}", x);
    println!("the assignment's own value: {:?}", assignment_result);
}
```

```
x at the start: 1
x now: 9
the assignment's own value: ()
```

`x = 9` is evaluated as an expression — `x`'s new value is written to memory — but this
expression's own value is `()`. The binding named `assignment_result` holds this `()`, not the
`9`. The only way to reach `x`'s new value is to read `x` itself; reading the assignment's
result never works, because there is no value there to read.

This completes an observation from the previous lesson: `if`, `match`, `loop`, and a block are
each a value, but `let` and assignment are not values. Two groups of writings, one saying
"this is a value" and the other "this is not a value," standing together in the same language
is a deliberate choice. A branch or a loop producing a value lets that writing's result be
carried directly into a binding; a binding itself producing a value would be meaningless,
because a binding is not itself a result, it is the act of storing a result. Producing a value
and allocating a place and writing to it remain two separate acts in the source, and the
classification does not mix the two.

#### Shadowing and mut: Same Result, Two Different Paths

There are two ways to rebind the same name in Rust. One is **shadowing**: the `let` keyword is
written again and a brand-new binding is set up; the old binding can no longer be reached by
that name, but it has not vanished. The other is `mut`: the binding is set up once with
`let mut`, and later lines write directly over it with `=`; there is no new binding here, only
the value the existing binding holds changes. The measurement below runs the two side by side, applying the
same two steps (add three, multiply by two) to each.

```rust
// binding.rs — the difference between shadowing and mut
fn main() {
    // shadowing: the same name is bound three times, each one a new binding
    let value = 5;
    let value = value + 3;
    let value = value * 2;
    println!("shadowing result: {}", value);

    // mut: bound once, changed twice (writing to the same binding)
    let mut counter = 5;
    counter = counter + 3;
    counter = counter * 2;
    println!("mut result: {}", counter);

    println!("equal: {}", value == counter);
}
```

```
shadowing result: 16
mut result: 16
equal: true
```

Both paths reach `16`, but even though they land in the same place, the way they get there
differs. On the shadowing side there are three separate `value` bindings — the second reads
the first and sets up a new binding, the third reads the second and sets up yet another new
one. On the `mut` side there is a single `counter` binding; both assignment lines write over
that same binding, setting up no new binding.

The question distinguishing these two paths is this: does the next line **read** the name the
previous line established, or does it **write** to the place the previous line established? In
shadowing, every `let value` line first evaluates the expression on the right (using the
previous `value`), then places this result into an entirely new binding — reading and setting
up a new binding are two separate steps. On the `mut` side, reading and writing happen on the
same binding; the line `counter = counter + 3` first reads `counter`'s current value, then
writes the new value to that same binding. The result in both is one addition and one
multiplication, but one reuses the name three times while the other never lets go of the same
name.

**LB9.** In shadowing the old binding is not "deleted," it only becomes unreachable by its
name. The first `value` (`5`) and the second `value` (`8`) can still exist somewhere in
memory, but no name in the source remains to reach them — with the third `let value`, that
name moved to the final binding. This behavior concerns memory management, the subject of the
Ownership and the Memory Model course; here what is observed is only this: shadowing reuses
the name, not the binding.

Shadowing reusing the name connects with another fact from an earlier lesson: a block is
itself an expression and carries its own scope. Shadowing inside a block loses its effect once
the block closes.

```rust
// shadowing_scope.rs — shadowing stays within its scope
fn main() {
    let value = 10;
    let inside = {
        let value = value * 100;
        value
    };
    println!("shadowing inside the block: {}", inside);
    println!("the outside value did not change: {}", value);
}
```

```
shadowing inside the block: 1000
the outside value did not change: 10
```

The inner block's `let value = value * 100;` reads the outer `value` and sets up a new binding
in its own scope; this new binding is valid only inside the block. When the block closes, the
outer `value` comes back completely untouched — the inner binding never wrote over the outer
binding, it only borrowed the same name within its own narrow scope. This is the second point
where shadowing departs from `mut`: a change made with `mut` recognizes no scope boundary, it
is visible everywhere the same binding exists; shadowing, with every `let`, sets up both a new
binding and that binding's scope anew.

#### Shadowing Can Change the Type

The difference between these two paths is not only mechanical, it is also a difference in
capability. Because shadowing sets up a brand-new binding every time, the new binding's type
does not have to be the same as the old one's.

```rust
// shadowing_type.rs — shadowing can change the same name's type
fn main() {
    let value = 21;
    let value = value * 2;
    let value = format!("result={}", value);
    println!("{}", value);
}
```

```
result=42
```

The first `value` is an integer, the third `value` is a string (`String`). All three carry the
same name, but all three are independent bindings; the last one does not have to know the
previous one's type, it only reads its value and sets up a new binding of a new type.

If the same writing is tried with `mut`, the compiler rejects it: after the line
`let mut value = 21`, a line trying to assign the result of a string-formatting call to
`value` does not compile, because `mut` does not set up a new binding, it only allows the
existing binding to be replaced with another value of the **same type**. Once a binding is set
up as `i32`, a `String` cannot be written to that binding — to the compiler, this is
equivalent to trying to fit two differently sized values into the same place. Shadowing does
not carry this restriction, because every `let` tells the compiler "allocate a new place
here"; `mut` says "write to the existing place," and that place's shape is fixed by the first
`let`.

This difference shows why shadowing is not merely a shortcut. In a chain of transformations
carrying the same name (such as parsing a string into a number, then turning that number into
a formatted string), it lets each step stay in its own natural type; a reader reads which type
applies at each step from the binding's most recent `let` line, without having to track the
type of intermediate steps. In the `shadowing_type.rs` example, this chain had three steps: it
started as an integer, was doubled as an integer, and was formatted as a string. If the same
chain were to be written with `mut`, either three separate names would be required
(`raw_value`, `doubled_value`, `formatted_value`), or an extra conversion step would be needed
to keep every step in the same type. Shadowing makes both of these options unnecessary: a
single name follows the logical steps of the transformation, while letting each step carry its
own type.

This capability also has a cost: tracking how many times the same name has been shadowed in a
row demands more attention from a reader than `mut` does. Because `mut` never changes a
binding's type, when a reader sees `counter` in the middle of the code, the type is clear from
the first `let mut`. In shadowing, giving the same guarantee requires going back to the
nearest `let value` line — a `value` three lines earlier may carry a different type. For the
rest of the course, shadowing will usually be used between short, nearby lines; this closeness
keeps type tracking from getting lost while reading.

#### Limiting Measurement: Immutability Is a Binding's Boundary

The previous two sections always showed immutability through a single value — an integer or a
string. It needs clarifying how immutability behaves on a value that holds several components
together. A binding set up without `mut` protects not only its own value but all of that
value's inner fields — you cannot separately change even a single component of a tuple.

```rust
// immutability_boundary.rs — the state of inner fields while the binding is immutable
fn main() {
    let point = (3, 4);
    println!("start: ({}, {})", point.0, point.1);

    let point2 = (point.0 + 1, point.1);
    println!("new binding: ({}, {})", point2.0, point2.1);
    println!("old binding unchanged: ({}, {})", point.0, point.1);
}
```

```
start: (3, 4)
new binding: (4, 4)
old binding unchanged: (3, 4)
```

`point` was set up with no `mut` at all. To "update" it, `point.0 = 4;` would have to be
written — this line does not compile, because `point` is an immutable binding, and this
immutability applies to every component of the tuple at once, not just the name visible from
outside. Instead, the code set up a new binding (`point2`) and read two of the old one's
components to compute a third value; `point`, as seen in the output's last line, never
changed. The new binding was set up not over the old one, but next to it; the two bindings
continue to exist at the same time, independent of one another.

**LB10.** This can read like "immutability is deep," but that is not the full picture. What is
shown here is that a binding **itself** is immutable — no component belonging to that binding
can be rewritten without `mut`. But whether the same data can be shared through another
binding or reference, and if shared, whether immutability holds from there too — that is the
question of ownership and borrowing rules, and it is the subject of the Ownership and the
Memory Model course. The boundary drawn here is this: a binding not taking `mut` guarantees no
change is allowed through that binding's own name; it is not a broader guarantee about the
whole of the data.

This boundary belongs to the same family as the previous two lessons' limiting measurements.
In Rust's Design Goals, a method's class was determined by looking at its signature, not its
call site; in Setup and the Package Manager, a binary's name was determined by looking at the
manifest, not the source. A similar pattern holds here: how "immutable" a binding is gets
determined only by looking at that binding's own declaration (whether `mut` is present or not)
— without looking at how the data is shared elsewhere. All three lessons repeat the same
lesson: when reading a guarantee in Rust, knowing **where** the guarantee comes from is as
important as the guarantee itself.

#### Summary

- `let name = value;` is a statement and can never be used as a value anywhere; the expression
  on the right is evaluated and bound to the name.
- An assignment itself also produces `()`; the only way to reach the new value is to read the
  name.
- Shadowing sets up a brand-new binding with every `let`; reassignment with `mut` writes over
  the existing binding. Both can reach the same numeric result.
- Only shadowing can change a binding's type, because a new place is allocated at every step;
  `mut` stays bound to the binding's initial type.
- A binding being immutable covers all of that binding's inner components, but it does not
  answer the question of whether the data can be shared some other way.
- Shadowing is bounded by scope: shadowing inside a block disappears when the block closes,
  without ever affecting the outer binding; a change made with `mut` recognizes no scope.

#### Next Step

The values bindings carry have not yet been examined one by one — only integers have been
seen. The next lesson descends into Rust's primitive types: integer, floating-point, boolean,
character, and tuple, and shows by running it what value each produces as an expression, and
why `()` counts as one of these types too. The binding concept established in this lesson will
not change there — only the variety of bound values will expand, and each type's own
boundaries (the range an integer can overflow into, a tuple's fixed length) will be measured
one by one.

### Primitive Types

Source: https://academia.sh/en/courses/rust-fundamentals/primitive-types

Integer, floating-point, boolean, character, and tuple — five primitive types that produce five separate values when run as expressions; but there is a sixth type too — (), the type with a single value — and that what a statement produces is not nothingness but exactly this type's one value is shown by printing the two values side by side in the same run.

The previous lesson took up the binding itself but did not enter the variety of bound values —
only integers were seen. This lesson fills that gap: what Rust's five primitive types
(integer, floating-point, boolean, character, tuple) produce when run as expressions, and why
the sixth type standing beside these five — `()` — is not "valueless," but, on the contrary, a
type in its own right.

Each of the five primitive types carries a fixed boundary built on top of the previous
lesson's immutability discussion. An integer has a specific bit width, and that width
determines the largest and smallest values it can hold; a floating-point number carries an
approximate precision; a boolean can hold only one of two states; a character represents a
single Unicode code point; a tuple consists of a fixed number of components, and that number
is known at compile time. Each of these boundaries is measured separately in this lesson.

#### Five Types, Five Values

The source below binds one value from each of the five primitive types and prints each one; it
then separates the tuple's components into separate names through destructuring.

```rust
// primitive_types.rs — the value each primitive type produces as an expression
fn main() {
    let integer: i32 = -7;
    let floating: f64 = 2.5;
    let boolean: bool = floating > 2.0;
    let letter: char = 'r';
    let pair: (i32, bool) = (integer, boolean);

    println!("integer: {}", integer);
    println!("floating-point: {}", floating);
    println!("boolean: {}", boolean);
    println!("character: {}", letter);
    println!("tuple: ({}, {})", pair.0, pair.1);

    let (first, second) = pair;
    println!("destructured first: {}", first);
    println!("destructured second: {}", second);

    let unit: () = ();
    let mut counter = 0;
    let statement_result = { counter += 1; };
    println!("explicitly typed unit: {:?}", unit);
    println!("what a statement produces: {:?}", statement_result);
    println!("are the two the same value: {}", unit == statement_result);
    println!("counter now: {}", counter);
}
```

```
integer: -7
floating-point: 2.5
boolean: true
character: r
tuple: (-7, true)
destructured first: -7
destructured second: true
explicitly typed unit: ()
what a statement produces: ()
are the two the same value: true
counter now: 1
```

The first five print lines repeat the previous lesson's definition that "an expression
produces a value" across five separate types: `-7` is an integer value, `2.5` is a
floating-point value, `floating > 2.0` produces a boolean value, `'r'` is a single character,
`(integer, boolean)` is a tuple holding two values together. `boolean` itself is worth
noticing too: what produces it is not a constant but a comparison expression — in Rust,
comparison operators are themselves expressions too, and return a value of type `bool`.

The type annotations for these five values (`i32`, `f64`, `bool`, `char`, `(i32, bool)`) are
deliberately written out explicitly here; Rust can infer most of them from the value on the
right, but the type itself always exists even if it is not written in the source — every
binding has a single fixed type, and that type is determined at compile time. The `integer`
variable carries the value `-7` and its type is `i32`; this is Rust's default integer type,
thirty-two bits wide, and can carry both negative and positive values. The `floating`
variable, in turn, is of type `f64`, a sixty-four-bit-wide floating-point number, and is
Rust's default floating-point type. A tuple's type is written in the form `(i32, bool)` — in
parentheses, comma-separated, the components' own types in order. A tuple's type is determined
not only by its component types but also by its component **count**: a two-element tuple and a
three-element tuple are different types even if their components are otherwise the same.

**LB11.** The line `let (first, second) = pair;` uses an access path different from the
`pair.0` / `pair.1` form seen in the previous section: instead of reading the tuple's
components one by one, it repeats the tuple's shape in a pattern and binds two new names in a
single line. This works by evaluating the `pair` expression on the right and matching it
against the pattern on the left; if the left side is in the shape of a tuple, the right side
must also be a tuple with the same number of components. Pattern matching's more general form
will be taken up separately later; here only this single form specific to tuples was used.

A tuple's fixed component count gives a different guarantee than a growable structure like
`Vec` from the previous lesson: from the moment `pair` is set up with two components, no third
component can be added and no first component can be removed anywhere in the source. This
fixedness is known at compile time and requires the pattern on the left of the destructuring
line to match the tuple on the right in exact component count — a writing like
`let (first, second, third) = pair;` does not compile, because `pair` carries only two
components. This is another view of a tuple's type being defined not only by its components'
types but also by their count.

#### Limiting Measurement: () Is Not Valueless, It Is Single-Valued

The output's last four lines carry this lesson's main claim. The binding named `unit` was
explicitly defined as type `()` and bound with the value `()` — this shows that `()` is a real
type, carrying a type name just like `i32` or `bool`. `statement_result`, on the other hand,
arrived by a completely different path: as the result of a statement that increments `counter`
by one inside a block. The two values were produced by different paths, but the equality test
gave `true` — because the `()` type has exactly one value, and both bindings hold that one
value.

This shows where the sentence "a statement produces no value" can be misunderstood. Saying a
statement produces no value would be misleading; the correct statement is that a statement
produces `()`'s single value. Nothingness and "the value of a single-valued type" are not the
same thing: nothingness means not even having a type, whereas `()` is a real type, its size is
defined, and it can be written explicitly as a return type in function signatures — indeed,
while a function like `double` in the previous measurement core writes `-> i32`, a function
whose body produces only a side effect writes no return type at all, because the default
return type is already `()`.

`f64` carries a boundary of its own among these six types: although the number of values it
can hold is finite, because the real numbers are infinite, not every decimal value finds an
exact counterpart — while some values like `2.5` are represented exactly, some simple
fractions are represented by an approximate value. This lesson does not measure this
approximation separately; here only what value `floating` itself produces as an expression was
shown, the boundaries of precision are a separate subject on their own.

Each of the other five types can hold more than one value — an `i32` can be one of billions of
different values, a `bool` at least one of two. `()` departs from this pattern: the number of
values it can hold is exactly one. This does not make `()` "less" than the other five types,
only different — if the amount of information a type carries is measured by how many separate
values that type can hold, `()` carries zero information, and that is exactly what a statement
wants to convey: the information that "this line ran," itself independent of what value was
produced.

If we order the six types by the number of values they hold, `()` sits at one end (one value),
`bool` right next to it (two values), then `char` (roughly a million Unicode code points'
worth), then `i32` and `f64` (billions of values); a tuple's own value count is the product of
its components' value counts. This ordering is not arbitrary: the amount of information a type
can carry is directly related to this number, and `()` sitting at the far end shows why a
statement is defined as "a result that carries no information."

This reading also explains why `()` differs from `bool`. Because `bool` carries two values, it
can encode a "yes/no" piece of information; because `()` carries a single value, it can encode
no distinction at all — knowing it was produced is not the same as knowing which option
occurred, because there is only one option. If a method's return type is `()`, you know that
method's call succeeded (otherwise the program would have crashed or the compiler would have
rejected it), but you cannot know what state the call produced — this is the same point as the
`Vec::push` example from the previous lesson: `push` guarantees an element was added, but gives
no further information about "how" the addition went.

#### Integer Overflow: A Defined Behavior

Integer types have a fixed bit width, and a sum that exceeds this width **overflows**. Rust
does not leave this case undefined; it requires you to explicitly choose what overflow does.
The **wrapping** (`wrapping_add`) method is one of these choices: it computes the result by
taking the value one past the type's largest value back to its smallest value.

```rust
// overflow_and_char.rs — the defined behavior of overflow and that a character is not a byte
fn main() {
    let largest: u8 = u8::MAX;
    let overflowed = largest.wrapping_add(1);
    println!("largest u8: {}", largest);
    println!("after overflow: {}", overflowed);

    let ascii_word = "resume";
    let accented_word = "résumé";
    println!("resume bytes: {} characters: {}", ascii_word.len(), ascii_word.chars().count());
    println!("résumé bytes: {} characters: {}", accented_word.len(), accented_word.chars().count());
}
```

```
largest u8: 255
after overflow: 0
resume bytes: 6 characters: 6
résumé bytes: 8 characters: 6
```

`u8` is an eight-bit integer type, and the largest value it can hold is `255`. Adding `1` to
`255` exceeds the type's boundary in ordinary addition; `wrapping_add` ties this excess to a
defined rule — it "wraps" the result into the type's range and returns to `0`. This shows that
overflow does not lead to random behavior: `wrapping_add` gives the same answer on every run,
on every machine, because the rule comes from the bit width itself, not from the runtime's
state at that moment.

This behavior is not a randomly chosen example, it is the counterpart of a real need: a value
expected to count cyclically and return to the start at a certain boundary — a day's position
within a week, a position in a circular buffer — needs exactly wrapping behavior.
`wrapping_add` meets this need with a single call, and the result makes visible, right in the
source, which rule handles the overflow. The standard library offers other methods in the same
family — ones that report overflow as an error, and ones that stop overflow at the type's
boundary; each gives a different defined answer to the same problem, but none of them leaves
it undefined.

**LB12.** An integer type's bit width (eight bits for `u8`, thirty-two bits for `i32`) is fixed
in the language itself and does not vary by machine — this differs from types like `usize`,
which depend on pointer width. This is exactly why `wrapping_add` gives the same result on
every run: what is measured is a boundary that belongs to the type's own definition, not to
the hardware.

#### Character Is Not a Byte

The second half of the same source measures the length of two words in two different ways.
`len()` gives the string's length in **bytes**, while `chars().count()` counts how many
**characters** the string contains. In the string `"resume"` the two come out equal — six
bytes, six characters — but in the string `"résumé"` they diverge: eight bytes, six characters.

This is because Rust encodes strings with UTF-8 — letters in the basic English alphabet are
represented by a single byte, while letters like `é` require two bytes. The `char` type itself
represents a single Unicode code point, not a byte in the string; so a `char` value is always
one character, but a byte is not always one character. The six-letter word `"résumé"` taking
up eight bytes is the concrete consequence of this distinction: code that tries to read the
word byte by byte would split the two accented letters from the wrong place; `chars()`
prevents this splitting and gives one complete code point each time. Placing the two words side
by side also shows that the difference only shows up in letters carrying diacritics — in a
string made up of plain ASCII characters, the byte count and the character count already
coincide, because every ASCII character is represented by a single byte.

The counterpart of this distinction in the source is this: when reading a string's length,
attention must be paid to which question is being asked. The answer to "how many bytes does it
hold" matters for storage and transfer over a network; the answer to "how many characters does
it contain" matters for the length to be shown to a user. Since the two can give different
numbers for the same string, a codebase not knowing which question it is asking can turn into
an unmeasurable class of bug.

The six types measured throughout this lesson (the five primitive types and `()`) were all met
with the same two questions: what is the set of values this type produces, and where does that
set's boundary come from. For integer and character, the boundary came from bit width; for
tuple, from component count; for `()`, from the type's definition — a single value. These
questions follow the same method as the previous two lessons' limiting measurements: drawing a
guarantee's scope by looking at the definition of the structure that gives the guarantee. In
the next lesson, these six types will be used as ready material, and the focus will shift to
how this material is gathered inside a body and turned into a single return value.

#### Summary

- Each of the five primitive types (integer, floating-point, boolean, character, tuple)
  produces a value of its own type when run as an expression; a tuple's components can be read
  both by index (`.0`) and by destructuring (`let (a, b) = ...`).
- `()` is a real type and has a single value; what a statement produces is not nothingness, but
  exactly this type's one value.
- Integer overflow is not undefined in Rust; methods like `wrapping_add` explicitly determine
  how overflow is handled, and the result is the same on every run.
- Fixed-bit-width integer types (such as `u8`, `i32`) do not vary by machine; this is a
  separate class from types that depend on pointer width.
- A `char` is a Unicode code point, not a byte; a string's byte length and character count can
  be different numbers.
- A tuple's type is determined by both its components' types and their count; the count is
  fixed at compile time and requires the destructuring pattern to match the tuple on the right
  exactly.

#### Next Step

The types of values are now defined, but most of the writings producing these values have so
far been single-line expressions. The next lesson asks how a **whole** body produces a value —
it shows by running it how whether a function's last line ends with a semicolon determines the
return value, and when that line requires writing `return`. `()` will appear there too: why
the return type of a function whose body produces only a side effect is valid even when never
written will be seen as a direct consequence of the definition this lesson established.

### Functions and Expression Orientation

Source: https://academia.sh/en/courses/rust-fundamentals/functions-and-expression-orientation

A function's body is a block, and a block is already an expression: if the last line has no semicolon, that is the body's — and therefore the function's — return value; add a semicolon and the body returns (); the semicolon discards the value, but the discarded value's computation still happens, and this is shown through the discarded value's own side effect.

The previous four lessons established, in turn, the three classes, the source's mandatory
skeleton, binding, and primitive types. This lesson brings them all together at a single
point: a function's body is a block, and a block was already defined as an expression in
Rust's Design Goals. A function's return value, therefore, is not a separate mechanism — it is
the body block's own value. This lesson carries the course's axis in its clearest form: the
presence or absence of a semicolon, by itself, determines what a function returns.

This explains why the concept of a "function" needs no separate return syntax in Rust. In many
writing styles, a function's return value is treated as its own structure, separate from the
rest of the body. Rust instead reuses a rule already established: what happens when a function
is called is that its body is evaluated as a block, and whatever the block evaluation's result
is, that is the function's result too. There is no new concept to learn — the "a block is an
expression" rule established in the previous lessons is enough to explain a function call in
its entirety here.

#### The Body's Last Line Is the Return Value

As seen in the previous lessons, a block gives the value of its last line if that line has no
semicolon; it gives `()` if it ends with a semicolon or is empty. Because a function's body is
a block too, the same rule applies to it: if the body's last expression takes no semicolon, the
function itself returns that value.

This rule applies not to every line of the body, but only to its last line. All the
intermediate lines in a function body are ordinary statements and usually end with a semicolon
— `let` bindings, calls with side effects, loops. Only the body's very last line stands in a
special position: that is **tail position**, the only place that determines the value of the
block itself (and therefore the function). The example below shows the effect of this
single-line rule across two different bodies.

```rust
// function_body.rs — the body's last line determines the return value
fn compute_and_log(x: i32) -> i32 {
    println!("computing: for {}", x);
    x * 2
}

fn as_expression(x: i32) -> i32 {
    compute_and_log(x)
}

fn as_statement(x: i32) {
    compute_and_log(x);
}

fn main() {
    let result = as_expression(21);
    println!("as_expression return value: {}", result);

    let statement_result = as_statement(21);
    println!("as_statement return value: {:?}", statement_result);
}
```

```
computing: for 21
as_expression return value: 42
computing: for 21
as_statement return value: ()
```

`as_expression` and `as_statement` wrap the same call — both call `compute_and_log(x)` — but
their bodies' last line was written differently. In `as_expression` the call has no semicolon,
the body's tail expression; in `as_statement` a semicolon was added to the end of the same
call. This one-character difference forced both functions' signatures to change too:
`as_expression`'s return type can be written as `-> i32`, while `as_statement`'s return type
was never written at all, because the default return type is `()` and the body now produces
`()`. These two signatures cannot stand in for one another — if `-> i32` were written on
`as_statement`, the compiler would reject it because its body produces `()`.

**LB13.** This shows the same fact as the previous lesson's `unit: ()` example, at the
boundary of a function. A function's return type must match the type of the value the body
produces exactly; if the body produces `i32`, the signature must write `-> i32` or a
compatible type, if the body produces `()`, the signature must either write `()` or write
nothing at all. This match is checked at compile time — a mismatch between body and signature
is stopped before it ever reaches runtime.

This check has a concrete consequence: changing only `as_statement`'s signature to `-> i32`
without touching its body is not enough, because the body still produces `()`. Likewise,
adding a semicolon to the tail call in `as_expression`'s body breaks the signature too — the
body would now produce `()` while the signature still wrote `-> i32`. Both cases are rejected
by the compiler; they are not placed into a source block because both are examples of a
rejection, but the result is this: this link between body and signature cannot be changed
one-sidedly anywhere in the source. Adding or removing a semicolon changes not only the body's
last line, but also the promise the function makes to the outside world.

#### Limiting Measurement: A Semicolon Silently Discards a Value

The output's first two lines are also worth noticing: the message "computing: for 21" was
printed twice — once for the `as_expression` call, once for the `as_statement` call. The
second call's result (`42`) was used nowhere, `statement_result` held only `()`. But the print
line inside `compute_and_log` still ran, because the function's body was run in full on both
calls; what changed was not what the result was, but where the result went.

This clarifies what a semicolon does: a semicolon does not cancel a computation, it only
discards that computation's **result**. The writing `compute_and_log(x);` first runs
`compute_and_log(x)` in exactly the same way as its semicolon-free form — every line inside it
runs, every side effect inside it (here, printing) happens — then leaves the produced value
`42` unsaved under any name. This discarding is **silent**: the compiler prints no warning, the
runtime leaves no trace; the only thing showing it was discarded is that the value itself
appears nowhere in the next line. The only way to make this silence visible in this lesson was
to place an independent side effect (printing) inside the computation — without the side
effect, we could not tell from the outside whether a multiplication was really running inside
`as_statement`.

This finding has a practical consequence: ignoring a function's return value does not mean
ignoring the work inside that function. The call `as_statement(21)`, even though its result is
never read, ran the entire body of `compute_and_log`. Whether a function is called only for a
side effect or only for a value is read from whether a semicolon is placed at the end of the
call in the source — both do the same work, the only difference is whether the result is read.

This observation also explains why a print macro call can carry both a side effect and a
return value: the call prints a line to the screen (side effect) and at the same time produces
the value `()` (return value). The two sides existing separately shows that using one does not
cancel the other — even if a call's result is not read, every line inside that call still
runs.

This distinction is a reverse-angle view of the previous lesson's `Vec::push` versus
`HashSet::insert` comparison. There, the same work (insertion) gave two separate methods two
separate classes, because the methods' signatures differed. Here, the same call
(`compute_and_log(x)`) can fall into two separate classes depending on how the caller writes
it: used as an expression if written without a semicolon, used as a statement if written with
one. There, the class came from the method's own signature; here it comes from the caller's
choice — but in both cases, the work inside the body stays the same, independent of the class.

#### Where return Is Required

Given that the body's last line determines the return value, when is the `return` keyword
needed? Answer: only when you want to exit before the body's natural end. If a function
reaches an early result inside a conditional branch and does not want to run the rest of the
body at all, it must write that early exit with `return` — because that branch is not in the
body's tail position, but in its middle.

This shows that `return` is a **choice**, not a requirement. Both functions in the previous
section were valid, and both compiled; the difference between them was how the body organized
branching. If a body can organize all its possible outcomes as branches of a single large
expression, that expression can be placed in tail position and the same result reached without
writing `return` at all. If a body wants to exit early in some cases without running the rest
of the logic at all, that exit **cannot** be in tail position — because tail position is, by
definition, the body's very end, and an early exit means precisely "not going all the way to
the end."

```rust
// early_return.rs — return is needed only to exit before the body's natural end
fn sign_with_return(x: i32) -> i32 {
    if x < 0 {
        return -1;
    }
    if x == 0 {
        return 0;
    }
    1
}

fn sign_with_expression(x: i32) -> i32 {
    if x < 0 {
        -1
    } else if x == 0 {
        0
    } else {
        1
    }
}

fn main() {
    for value in [-5, 0, 5] {
        println!("{}: with_return={} with_expression={}", value, sign_with_return(value), sign_with_expression(value));
    }
}
```

```
-5: with_return=-1 with_expression=-1
0: with_return=0 with_expression=0
5: with_return=1 with_expression=1
```

Both functions produce the same three results, but only `sign_with_return` uses `return`. In
`sign_with_return`, every `if` block stands in the middle of the body — the body continues
after them with the line `1` — so a branch wanting to exit early must declare its own value
explicitly with `return`; otherwise that `if` block itself is evaluated as an expression, but
its result is bound nowhere and the body continues on to its own natural end. There is a point
worth noticing here: the `if` block itself in the writing `if x < 0 { return -1; }` still
produces `()`, because `return` jumps from inside the block to the whole function, leaving no
value to the block itself. `return`'s effect crosses the block boundary; while the semicolon
and tail-position rules only determine that block's own value, `return` jumps directly to the
outermost function call. In `sign_with_expression`, on the other hand, all the branching was
gathered into a single `if`/`else if`/`else` chain, and this chain itself became the body's
tail expression; no branch exits early, all three are evaluated as part of the same large
expression, and the branch the chain selects becomes the function's return value directly.

**LB14.** `return` does not change an expression's value; it determines **when** an expression
is evaluated, or more precisely, that the rest of the body will not be evaluated at all. In
`sign_with_return`, if `x < 0` is true, no line after `return -1;` runs — not even the second
`if` block is evaluated. In `sign_with_expression`, because all three branches are part of the
same chain, only one is selected, but there is no concept of "exiting early"; even though the
branches not selected are never evaluated, this is ordinary branching behavior, not an early
exit.

The output does not directly show this difference — for all three values, the two functions
give the same result, because both encode the same logic with a different arrangement. To see
where the difference lies, one must look not at the number of lines that ran, but at the
**shape of the control flow** in the source: in `sign_with_return`, for a negative input, only
one `if` body runs and the function exits right there; in `sign_with_expression`, for the same
input, all three branches are evaluated as part of the same chain, and only one is selected.
Both approaches reach the correct result, but one has a moment that can be called an "early
exit," the other has none at all — there, there is only a selection.

The choice between these two writing styles is a question of readability, not correctness:
both produce the same three results, and both are equally valid. But as a body grows longer
and the number of special cases requiring early exit grows, the form written with `return`
generally simplifies the rest of the body by moving special cases out of the tail expression to
their own control points; bodies simple enough to be expressed with a single branch can reach
the same result with a single tail expression, without writing `return`.

`sign_with_return`'s structure makes this concrete: two special cases (`x < 0`, `x == 0`) are
handled in their own control lines and separated from the rest of the body, while the one
remaining general case (`1`) sits at the body's natural end, tied to no condition. A reader can
read the first two `if`s as "special case, exit here," and the last line as "this is now the
one remaining possibility." In `sign_with_expression`, all three cases are presented with
equal weight, as parts of a single chain — none is "earlier" or "more exceptional" than
another. Both writings are correct, but the signal they give a reader differs: one says "most
of the time it is the one line left behind, everything before it is a deviation," the other
says "all three cases matter equally."

There is one more pattern in functions written with `return`: the body's natural end usually
carries the most general or most expected case, because reaching it means having passed all
the earlier special checks. This gives a reader going through the source top to bottom a kind
of ordering: the most commonly reached path is reduced to a minimum, and special cases are
pulled forward and eliminated. This ordering is not a rule Rust requires; it is a habit born
from `return`'s natural way of being used.

This lesson showed why the three classes (expression, statement, item) established in the
course's first lesson form a valid frame not only for individual lines, but for a **whole
function body** too. An `fn` declaration is an item and produces no value itself; but that
item's body is a block, a block is an expression, and what an expression produces — here too,
as always — depends on whether its last line carries a semicolon. The smallest unit in the
source (a single line) and one of the largest units (an entire function) can be read by the
same rule; the classification established at the start of the course gains no new exception as
its scale grows.

#### Summary

- A function's body is a block; a block is already an expression, and produces the value of
  its last line if that line has no semicolon, `()` if it does — the function's return value
  is this produced value.
- The type of the value the body produces must match the function's declared return type
  exactly; this match is checked at compile time.
- A semicolon does not cancel a computation, it only silently discards its result; side effects
  inside the discarded computation still happen.
- `return` is needed only to exit before the body's natural end; if all branching can be
  gathered into a single tail expression, `return` is never written.
- Two writings that produce the same result (with an early exit and with a tail expression) are
  equally valid; the choice concerns readability, not correctness.

#### Next Step

In this lesson, the `if`/`else if`/`else` chain was already used as an expression, but which of
the five control-flow forms (`if`, `match`, `loop`, `while`, `for`) produce a value was not
measured one by one. The next lesson runs these five side by side and shows why an `if`'s
branches always have to be the same type, and when a loop produces a value and when it never
does. That all three branches of `sign_with_expression` return `i32` was not a coincidence — if
one of the branches returned a different type, this body would not compile at all; the source
of this restriction is the next lesson's subject.

### Control Flow

Source: https://academia.sh/en/courses/rust-fundamentals/control-flow

if, match, loop, while, for — of the five control-flow forms, only three can produce a value: if and match are each an expression and require all their branches to give the same type, loop gives a value only through break, while and for never do; a labeled break can carry a value directly out of nested loops.

The previous lesson already used the `if`/`else if`/`else` chain as an expression, but it
showed the state of only one of these five control-flow forms (`if`, `match`, `loop`, `while`,
`for`). This lesson runs all five side by side and asks each of them a single question: does
this form produce a value where it is used, or does it only do a piece of work?

These five forms are grouped under a single category in most languages — "control-flow
constructs" — because all of them determine which lines of a program run in what order. Rust
preserves this common function, but further splits the five into two: some forms also produce
a value while directing flow, some forms only direct flow. This split is not arbitrary — it
stands on the same axis as the question the course has asked from the start, and it is answered
once and for all in this lesson across the five forms.

#### Five Forms, Three Answers

```rust
// control_flow.rs — how many of the five forms produce a value
fn main() {
    let number = 7;

    let if_result = if number % 2 == 0 { "even" } else { "odd" };
    println!("if: {}", if_result);

    let match_result = match number {
        0 => "zero",
        n if n % 2 == 0 => "even",
        _ => "odd",
    };
    println!("match: {}", match_result);

    let mut counter = 0;
    let loop_result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 10;
        }
    };
    println!("loop: {}", loop_result);

    let mut total = 0;
    let while_result = while total < 3 {
        total += 1;
    };
    println!("while: {:?}", while_result);

    let mut list_total = 0;
    let for_result = for element in [1, 2, 3] {
        list_total += element;
    };
    println!("for: {:?}", for_result);
    println!("list_total: {}", list_total);
}
```

```
if: odd
match: odd
loop: 50
while: ()
for: ()
list_total: 6
```

The five forms give three separate answers. `if` and `match` each produce a value —
whichever branch is selected, they give that branch's value. `loop` also produces a value, but
only when a value is given to `break`; here, once `counter` reaches five, `break counter * 10`
runs, and `loop` itself turns into the value `50`. `while` and `for`, in turn, never produce a
value — both give `()` in every case, whatever the loop body does. The `list_total` variable
inside the `for` loop makes this clear: the loop's own value is `()`, but a side effect inside
its body (updating an outer variable) does the loop's real work.

These three answers are not randomly distributed; each one's source lies in the form's own
design. `if` and `match` are **selective** — they offer several branches, select exactly one,
and the language's natural choice is to count the selected branch's value as the value of the
whole expression. `loop` is **unbounded** — it does not know on its own when it will stop, so
it can produce a value only when one is explicitly given to `break`; if `break` is never
called, `loop` never ends either, and the question of its "value" does not even arise. `while`
and `for`, in turn, are **conditional** — every turn of the loop depends on a condition (an
explicit boolean expression in `while`, whether an iterator is exhausted in `for`), and the
loop stops on its own when that condition is false. Which turn this natural stop happens on is
not visible from the source, so it would be meaningless for the language to give these two
forms a value — the source itself does not say which state counts as the "result."

This threefold split has one more consequence: because `loop` is unbounded, it can actually set
up the same flow as `while true { }` — neither stops on its own. But only `loop` can produce a
value, `while true { }` cannot; because `while` itself, whatever its condition is, never offers
`break` the ability to carry a value. The same unbounded flow, written with two different
forms, ends up with two different capabilities.

**LB15.** `if` and `match` producing a value comes with one condition: all their branches must
give a value of the **same type**. In the `if_result` example above, both branches give
`&str`; in `match_result`, all three branches give `&str`. If one branch gave a different type
(say, one branch `&str`, another `i32`), the compiler would reject it — because `if_result` or
`match_result` must have a **single** type, and that type must be known at compile time,
regardless of which branch is selected.

The second branch in `match_result` (`n if n % 2 == 0 => "even"`) carries a **match guard**:
the pattern `n` captures `number`'s value, and `if n % 2 == 0` ensures this branch is selected
only when the captured value is even. This is a broader example of pattern matching that will
be taken up separately later; here it is enough to see that an additional condition can decide
whether a branch is selected — its result is still that the branch produces a single value
("even").

This restriction explains another property, also seen in `match`'s branch carrying a match
guard: how many branches a `match` carries and under which conditions the branches are
selected may not be known until runtime, but the branches' **type** is always known at compile
time. The compiler can check the type of all branches without knowing which branch will run,
because type checking looks at the whole of the source, not just the single branch that will
be selected at runtime. This shows how type safety can be ensured independently of which
branch is selected: each branch is checked separately, and then all of them are confirmed to be
the same type — before any of them runs.

#### Carrying a Value Out of a Labeled Loop

`loop` producing a value with `break` raises a problem in nested loops: `break` breaks the
nearest enclosing loop, but sometimes a value needs to be pulled out of a loop **further out**.
Rust solves this with loop labels.

```rust
// labeled_loops.rs — a value carried out from a nested loop
fn main() {
    let target = 6;
    let mut i = 1;
    let result = 'outer: loop {
        let mut j = 1;
        loop {
            if j > 5 {
                break;
            }
            if i * j == target {
                break 'outer (i, j);
            }
            j += 1;
        }
        i += 1;
        if i > 5 {
            break (0, 0);
        }
    };
    println!("pair found: {:?}", result);
}
```

```
pair found: (2, 3)
```

Here two `loop`s are nested. The inner loop has no label, and its own `break` breaks only
itself — when `j` exceeds five, this inner loop ends and the outer loop continues by
incrementing `i`. The outer loop carries the label `'outer`; the line `break 'outer (i, j)`,
right from the middle of the inner loop, breaks the outer loop directly and carries the tuple
`(i, j)` into `result`. When the product `i=2, j=3` gives the target (`6`), this line runs and
the loop chain ends immediately.

This pattern is familiar to any code searching a two-dimensional space: finding the first pair
satisfying a given condition in a table, a grid, or a two-variable search space. If an
unlabeled `break` were used, only the inner loop could be broken when the condition was met;
the outer loop would continue, incrementing `i` and continuing the search — a separate flag
variable to store the result and an extra check in the outer loop would be needed. A labeled
`break` removes this extra checking and makes the search result directly the loop chain's own
value.

**LB16.** A labeled `break` does not only choose which loop is broken, it also determines which
loop a value belongs to. The value of `result` is the value of the outermost `'outer loop` —
the inner loop itself never produced a value, and cannot, because the inner `break;` carries no
value at all. The ability to return a tuple belongs only to the labeled loop; the inner loop
only controls its own flow, it cannot directly touch the outer loop's result — that is possible
only with a labeled `break`.

This is why the two `break` lines in the source do different jobs even though they carry the
same keyword. The unlabeled `break;` breaks the nearest enclosing loop — here, the inner loop —
and carries no value, because the inner loop was never bound to a result. The labeled
`break 'outer (i, j)`, in turn, jumps directly to the named loop no matter how many loops lie
in between, and determines that loop's value. These two uses can stand side by side in the same
lesson, in the same file, because the label only changes the **target**; the syntax and keyword
stay the same.

Labeling applies not only to `break`, but also to `continue` — moving to the next turn of a
labeled loop uses the same `'name:` syntax too. This lesson measured only the value-carrying
`break`, because the course's axis is value production; labeling `continue` follows the same
targeting logic, it just carries no value.

#### Limiting Measurement: An if Without else Is Also an Expression

Every `if` seen up to this point carried an `else` branch. An `if` with no `else` branch is
also an expression — only its value is always `()`.

```rust
// if_without_else.rs — an if without else is also an expression, its value is ()
fn main() {
    let number = 4;
    let result = if number % 2 == 0 {
        println!("even found: {}", number);
    };
    println!("the value of an if without else: {:?}", result);
}
```

```
even found: 4
the value of an if without else: ()
```

`result` was genuinely bound to a binding — this proves `if` was evaluated as an expression,
because Rust does not let you bind a statement to a binding. But `result`'s value is `()`,
because what happens when the condition is not met was never written, and Rust fills this gap
with `()` — as if there were an invisible `else { }`.

This is the else-less counterpart of the rule that `if`'s branches must "give the same type":
if the body of an `else`-less `if` tried to produce an `i32` like `number`
(`if number % 2 == 0 { number }`, with no `else` written at all), the compiler would reject it.
Because the unwritten `else` branch is implicitly counted as producing `()`, and if the body
produces `i32`, the two "branches" (the written body, the implicit `else`) would be different
types — just as the two branches in the `if_result` example in the previous section had to be
the same type. If the body of an `else`-less `if` is going to produce a value, that value can
be nothing other than `()`; otherwise a semicolon must be added to the end of the body, silently
discarding the value.

This boundary gives a shortcut when writing source: if an `if` is written only for a side
effect (a print, an update) and there is nothing to do in the opposite case, there is no need
to write an `else` branch — the compiler already expects `()`, and the body already produces
`()`. `else` becomes mandatory only when both branches need to produce a **different** value.

This is an observation from the same family as the previous lesson's semicolon rule. There,
adding a semicolon to a function body's last line turned the value the body produced into
`()`; here, never writing an `if`'s `else` branch reaches the same result by a different path —
the unwritten branch is implicitly counted as producing `()`. Both carry the same principle:
in Rust, a "missing" value is never left undefined anywhere in the source; the missing place
always corresponds to `()`.

The five forms splitting into three also offers a choice when writing source: do you need a
structure that selects a computation among a fixed number of branches (`if`, `match`), or an
open-ended repetition whose stopping condition is not known in advance (`loop`), or a
repetition whose end is known in advance from a condition (`while`, `for`)? These three
questions are independent of one another, and each one's answer brings with it whether that
form will produce a value. Before a programmer asks "will this loop give me a result," it is
enough to look at which loop form was chosen — the answer is hidden in the form itself.

This lesson brings together, in a single place, all the pieces this six-lesson section has
gathered. The first lesson defined the three classes (expression, statement, item) and put them
into numbers with a measurement core; the second lesson showed these classes hold at the
project level too; the third lesson established binding; the fourth lesson established the
types of bound values; the fifth lesson showed how a function body gathers these three classes
into a single return value. This lesson, finally, measured how control flow itself — the five
forms determining which line of a program runs when — fits the same three classes. Nothing
written in the source fell outside this classification: from the smallest operator to the
largest control structure, every line either produces a value, produces `()`, or only declares
a name. This also explains why the single question asked at the start of these six lessons —
does what you write in the source produce a value — did not stay confined to a single lesson
but could carry an entire section: the question does not grow, only the writing it applies to
widens.

#### Summary

- Three of the five control-flow forms can produce a value: `if` and `match` always (by
  selecting a branch), `loop` only when a value is given to `break`.
- `while` and `for` never produce a value; both give `()` in every case.
- All of `if`'s and `match`'s branches must give a value of the same type; otherwise the
  compiler rejects it.
- A labeled `break` can carry a value directly out of nested loops to the outermost labeled
  loop; unlabeled loops can only control their own flow.
- An `if` without `else` is also an expression, and its value is always `()`; it does not
  compile if its body tries to produce a different type.
- Nothing written in the source falls outside the expression / statement / item
  classification; this lesson showed that even control flow's most complex forms fall into one
  of the same three answers.

#### Next Step

Across these six lessons, whether control flow produces a value was measured by looking at each
form one by one. The next topic carries the same question into **data**: how a type is built,
how many states it carries, and how those states are taken apart — a new section starting with
structs and enums will take up these questions one by one. The types seen so far were the
ready-made types the language itself provides (integer, floating-point, tuple); the next
section takes up how new types are **defined** from these — fields gathered together, states
counted together.

## Data Modeling

Gathering fields and methods under a single type, counting states under a single type through data-carrying variants, pattern matching's need for exhaustiveness and destructuring, the difference between a growable array and a key-value structure, the separate guarantees given by ordered and hash-based structures, and what a module boundary means for visibility.

### Structs

Source: https://academia.sh/en/courses/rust-fundamentals/structs

The same Point value is built three separate ways — full syntax, update syntax, associated function — and all three produce a value as an expression; the struct declaration itself produces no value, it only introduces a name the following lines can use.

The Control Flow lesson measured, one by one, whether each of five control-flow forms
produces a value: `if` and `match` were expressions, `loop` carried a value only through
`break`, and `while` and `for` never gave one. This lesson carries the same question to
different material. There are no longer control-flow forms but data types, and the
question does not change: is constructing a value from a type an expression, or is writing
the type itself to the source an expression?

In Rust, the way to gather several fields under one type is a struct. This lesson measures
the difference between a struct's **declaration** and the **construction** of a value from
that struct using three separate syntaxes — full syntax, update syntax, and an associated
function — and shows that all three fall into the same class. The declaration itself never
falls into that class. The second half of the lesson returns to what the declaration
contains, namely fields: the same set of fields behaves quite differently across two forms
of struct — the named and the unnamed — and that difference is measured separately.

#### A Declaration Is an Item, Constructing a Value Is an Expression

The line `struct Point { x: i32, y: i32 }` produces nothing when it runs. This line is not
a computation but a declaration that introduces a name to the compiler: it is the
precondition for later lines being able to use the name `Point`, but it is not itself a
value. In the previous lesson's term, this is an **item** — it does not "run" at any point
in the source, it merely sits there and adds a type the compiler will know about.

By contrast, the syntax `Point { x: 3, y: 4 }` produces a value when it runs: it takes two
integers and packs them into a single value of type `Point`. This is an **expression** —
its result can be bound to a variable, passed as an argument to a function, appended to a
list. The difference here is exactly the distinction the previous lesson set up: one sits
there without being evaluated, the other is evaluated and gives back a value.

**DM1.** — The claim that a `struct` declaration by itself produces no output is not
verified here by a separate run but by the structure of the program below: the line
`struct Point { ... }` sits before `main`, not inside it, and no print call triggers that
line. The only output the program shows when it runs comes from the lines where the name
`Point` is **used**.

The reason behind this distinction is that the two kinds of lines are processed by the
compiler at different times. A declaration is read at compile time: the compiler records
the name `Point`, its fields, and their types into a table, but this recording leaves no
trace at the program's runtime. A value-construction syntax, however, acts at runtime:
when the line `Point { x: 3, y: 4 }` executes, two integers are actually assembled in
memory. This is the same order as a binding built with `let` (the subject of the Variables
and Immutability lesson) taking place at runtime — the difference is that what sits to the
left of a binding must be an expression, not an item.

This is one of the places where the three classes separate most clearly. A **statement**
is still evaluated — only its result falls to `()` or is discarded — but an **item** is
never evaluated at all; there is nothing that "runs" where it sits in the source. A
`struct` declaration falls into this third category: it is not a step passed over while the
program runs, but a record the compiler processes once while reading the source.

The run below places three different ways of constructing a value from the same `Point`
type side by side.

```rust
// struct_construction.rs — a struct declaration is an item, constructing a struct value is an expression
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn origin() -> Point {
        Point { x: 0, y: 0 }
    }

    fn magnitude(&self) -> i32 {
        self.x.abs() + self.y.abs()
    }
}

fn main() {
    let a = Point { x: 3, y: 4 };
    println!("{:<21}: x={} y={}", "full syntax", a.x, a.y);

    let b = Point { y: 9, ..a };
    println!("{:<21}: x={} y={}", "update syntax", b.x, b.y);

    let c = Point::origin();
    println!("{:<21}: x={} y={}", "associated function", c.x, c.y);

    println!("{:<21}: {}", "method result", b.magnitude());
}
```

```
full syntax          : x=3 y=4
update syntax        : x=3 y=9
associated function  : x=0 y=0
method result        : 12
```

**Full syntax** gives every field one by one: `x` and `y` are written explicitly, and the
result is a value with `x=3 y=4`. **Update syntax** (`..a`) is a shorthand for that: only
the `y` field is given again, and the remaining fields are copied from `a`. The result is
still a single `Point` value produced in one call — the syntax differs, the class does not.
**The associated function** (`Point::origin()`) reaches the same class by an entirely
different route: without ever writing `x` and `y`, the function call itself produces a
`Point`. What the three syntaxes share is that all three can be bound to a name with
`let` — something an expression can do by definition, and something meaningless for an
item.

#### Method vs. Associated Function: the Receiver's Presence

The calls `Point::origin()` and `b.magnitude()` look similar on the surface: both are
defined inside an `impl Point` block, and both return a value. The difference between them
shows up in the call syntax and comes down to a single point: **the presence of a
receiver**.

The `origin` function's signature has no `self`; it is therefore called through the type
name (`Point::origin()`) and requires no instance. In Rust's own terms this is an
**associated function** — it is associated with the `Point` type, but not with any
particular `Point` value. The `magnitude` function's signature, on the other hand, has
`&self` as its first parameter; this parameter states which value the call is being made
on, and it does not appear at the call site — the `b` in `b.magnitude()` takes its place.
This is what Rust calls a **method**.

The distinction is measured not by what the work does but by what the signature requires:
with a `self` parameter, the call goes through a value (method); without one, through the
type name (associated function). In the run above, `origin()` produced a value from
nothing, needing no existing `Point`; `magnitude()` could only be called through the
already-existing value `b`. Both are expressions — both return a value — and this adds no
new branch to the expression/statement/item axis: every Rust function call that does not
return `()` is already an expression, whether method or associated function. The
distinction lies outside that axis, in the call syntax, in whether a receiver is present.

The `magnitude` function computes the point's distance from the origin as the sum of two
absolute values: `self.x.abs() + self.y.abs()`. In the run, `b` carried the value `x=3
y=9`, so the `magnitude()` call gave `3 + 9 = 12`. `origin`, as its name suggests, plays the
role of a constructor: it is not the only one of the three ways to produce a `Point` value,
but a common naming habit — presenting a type's most common starting value through an
associated function like `Type::origin()`, `Type::empty()`, or `Type::default()`. There is
no limit at the declaration stage on how many such functions there can be; the `impl Point`
block gathers all of them in the same place.

Named-struct syntax asks for a field name only when constructing a value; a field name
never appears at a method call. In the syntax `b.magnitude()`, `b` states which value the
call is made on, but inside the `magnitude` function, the fields `self.x` and `self.y` are
reached by name. Even though the receiver appears reduced to a single dot (`.`) in the call
syntax, inside the function body it is the full `Point` value whose fields are reached by
name.

The `self` parameter carries a single `&` sign in front of it. The exact meaning of this
sign, and why the receiver can be used without being copied, is the subject of the
Ownership and the Memory Model course; what is measured here is only how the receiver's
**presence** determines the call syntax, not how the receiver is passed.

There is no upper bound on how many associated functions or methods a type's `impl` block
can hold. Even a small type like `Point` can have several constructor associated
functions — alongside `origin()`, names such as `unit() -> Point` or `pair(x: i32, y: i32)
-> Point` could offer other default values. Which names get used is a naming habit, not a
language rule; the compiler treats all of them as the same class, the associated function.

#### Field Order Carries No Identity in a Named Struct

When constructing a struct value, the order in which the fields are written in the source
does not have to match the order in the declaration. `Point { x: 3, y: 4 }` and `Point { y:
4, x: 3 }` produce the same value; the compiler matches each field by its name, not its
position.

```rust
// field_order.rs — field order carries no identity in a named struct
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let d = Point { x: 3, y: 4 };
    let e = Point { y: 4, x: 3 };
    println!("{:<18}: x={} y={}", "d (x first)", d.x, d.y);
    println!("{:<18}: x={} y={}", "e (y first)", e.x, e.y);
    println!("{:<18}: {}", "d and e equal", d.x == e.x && d.y == e.y);
}
```

```
d (x first)       : x=3 y=4
e (y first)       : x=3 y=4
d and e equal     : true
```

`d` and `e` were written in different order in the source, but both carry the same `x` and
`y` values. A field name here is not a position marker but a matching key: the writing
order is a matter of readability preference, it does not change the meaning.

This is an exception that falls outside Rust's general call convention. In a function call,
arguments are matched by position — in the syntax `double(3)`, which parameter the `3`
goes to is decided by its position in the parameter list, not by a name. Struct-construction
syntax is the one exception to this: each `field: value` pair inside `{ }` finds its match
in the `struct` declaration by name. This privilege belongs only to **construction**
syntax; when accessing a struct value's field (as in `a.x`), it is already accessed by
name, and no "order" question ever arises there.

**DM2.** — The claim that "order does not change meaning" is measured here not by a
separate field-comparison method but directly by the line `d.x == e.x && d.y == e.y`; this
line reads that both values' fields are equal to each other one by one, it does not compare
`d` and `e` themselves.

#### Position Is the Only Identity in a Tuple Struct

The exact opposite of the named struct is the tuple struct: its fields have no name, only a
position and a type. The declaration `Color(u8, u8, u8)` builds a type that carries three
bytes, but it is not name but **position** that decides which of these three bytes is "red"
and which is "blue."

```rust
// tuple_identity.rs — position is the only identity in a tuple struct
struct Color(u8, u8, u8);

fn main() {
    let red = Color(220, 20, 20);
    let Color(r1, g1, b1) = red;
    println!("{:<18}: r={} g={} b={}", "Color(220,20,20)", r1, g1, b1);

    let blue = Color(20, 20, 220);
    let Color(r2, g2, b2) = blue;
    println!("{:<18}: r={} g={} b={}", "Color(20,20,220)", r2, g2, b2);
}
```

```
Color(220,20,20)  : r=220 g=20 b=20
Color(20,20,220)  : r=20 g=20 b=220
```

This is the **limiting measurement**: while field order was free in a named struct (the
previous run showed this with `d` and `e`), position is the sole identity in a tuple
struct. `Color(220, 20, 20)` and `Color(20, 20, 220)` do not carry the same three numbers —
in the first, the first position is large; in the second, the third position is — and the
result is two different color values. In a named struct, a name answers the question "which
field"; in a tuple struct, only position answers that same question. In the writing
`Color(20, 20, 220)`, which field the first two `20`s belong to is resolved by looking at
**where** that value sits.

**DM3.** — The claim "position is the only identity" in a tuple struct is measured here by
placing the same three numbers into different positions, that is by producing the pair
`Color(220, 20, 20)` and `Color(20, 20, 220)`: the two calls carry the same three values (a
permutation of `20`, `20`, `220`), but because their positions differ, so do the results.

Destructuring also comes into play here: the line `let Color(r1, g1, b1) = red;` binds the
three fields of the value `red` in order to the names `r1`, `g1`, `b1`. This writing is in
fact a pattern — it matches the shape of the value on the right against the names on the
left — and the full scope of taking values apart with patterns is the subject of the next
lesson. Here it is only visible that accessing a tuple struct's fields goes through this
route: where `a.x` can be written for a named struct, a tuple struct's field is reached
either by position (`color.0`, `color.1`, `color.2`) or by destructuring, not by name.

A single-field tuple struct is also used to keep two quantities of the same underlying type
distinct from each other. `struct Pixel(i32);` and `struct Millimeter(i32);` both carry an
`i32` inside, but for the compiler they are two separate types; a `Pixel` value does not
stand in for a `Millimeter` value, even though its field looks the same. This is the tuple
struct's most common use: not giving an existing type a new name, but a new **identity**. A
named struct reaches the same goal with a field name (`Location { pixel: i32 }`); a tuple
struct does it with a single wrapper field, no field name ever written.

**DM4.** — The contrast between the named struct and the tuple struct is measured by
placing two separate runs side by side: `field_order.rs` changes the order and shows the
result stays the **same**, `tuple_identity.rs` changes the position and shows the result is
**different**. Both runs use the same three inputs (three numbers); what changes is only
whether the type is named or unnamed.

A struct is Rust's most direct way of holding data together: a fixed number of fields, each
tied to a single type, all present at once. The three syntaxes this lesson measured — full
syntax, update syntax, associated function — all arrive at the same result because all of
them fill the same fixed set of fields. But not every struct value is bound to a fixed set
of fields; the data a program has to model often takes more than one possible shape in an
"either this or that" way. A struct alone cannot meet this — a `Point` always carries both
`x` and `y`, there is no third "carries nothing" state. The next lesson builds the type
that fills exactly that gap.

#### Summary

- A `struct` declaration is an item: it produces no value when it runs, it only introduces
  a name.
- All three syntaxes for constructing a struct value — full syntax, update syntax,
  associated function — are expressions and produce a value.
- The difference between a method and an associated function is the presence of a receiver
  (`self`): a method is called through a value, an associated function through a type name.
- Field order carries no meaning in a named struct; fields are matched by name.
- A tuple struct's fields have no name, only a position; position is the sole identity and
  the result changes when it is changed.

#### Next Step

This lesson measured how many fields a struct value carries and how those fields are
constructed — but a struct value always carries the same set of fields. The next lesson
carries the same question to a different shape: can a type have more than one form, where
which fields it carries **changes depending on state**? An enum sets up a value being able
to carry one of several separate states under the same type; the next question is how these
states are read from the source.

### Enums

Source: https://academia.sh/en/courses/rust-fundamentals/enums

A Shape value can be constructed in three separate forms — carrying no data, carrying one number, carrying two numbers — and all three are counted under the same type; constructing a variant is an expression, the enum declaration itself is an item. The limiting measurement shows that variant count must not be confused with state count.

The Structs lesson measured that a `Point` always carries both `x` and `y`: a struct is a
value bound to a fixed set of fields, with a single shape. But the data a program has to
model often takes more than one possible shape in an "either this or that" way — a shape
that has not been drawn at all, a shape that is a circle, or a shape that is a rectangle.
A struct alone cannot meet this; the enum, the subject of this lesson, fills exactly that
gap.

An enum sets up a value being able to carry one of several separate **variants** under the
same type. The same question from the previous lesson applies here too: does constructing a
variant produce a value when it runs, or does the `enum` declaration itself produce
something? The answer follows the same order as structs, but this time the difference
between **variant count** and the number of states a value can carry is measured as well.

What separates an enum from a set of constants that do not serve the same function is that
each variant can optionally carry its own data. Three separately defined constants — say
`EMPTY`, `CIRCLE`, `RECTANGLE` — are three independent names; there is no guarantee the
compiler gives for the question "which one is selected." An `enum`, however, builds a
single type, and every value of that type knows within itself which variant it currently
carries. The first thing this lesson measures is how this single-typedness looks on the
expression/statement/item axis.

#### The Declaration Is Still an Item, Constructing a Variant Is an Expression

The line `enum Shape { Empty, Circle(f64), Rectangle(f64, f64) }`, just like the `struct`
declaration in the previous lesson, produces nothing when it runs. This line introduces the
compiler to a type with three separate variants; it is itself an item. By contrast, the
syntax `Shape::Circle(2.0)` produces a value when it runs — not an integer, but a `Shape`
value carrying the `Circle` variant with `2.0` inside it. This is an expression; its result
can be bound to a variable, appended to a list, passed to a function.

**DM5.** — The claim that "an `enum` declaration by itself produces nothing" is verified
here, just as with the `struct` example in the previous lesson, not by a separate run but
by the structure of the program below: the line `enum Shape { ... }` sits before `main`,
and no print call directly triggers that line; every visible output comes from the lines
where variants are **constructed** and **used**.

`Empty`, `Circle`, and `Rectangle` are three separate variant names, and all three are
listed, separated by commas, inside a single `enum Shape { }` declaration. The difference
between `Circle(f64)` and `Rectangle(f64, f64)` is the number of types inside the
parentheses: one carries a single `f64`, the other carries two `f64`s; `Empty` does not
even have parentheses, because it carries no data at all. These three forms — no
parentheses, one field, several fields — cover the full range of variant forms an `enum`
declaration allows.

The run below constructs four values of type `Shape` and prints which variant each one
belongs to and what its area is.

```rust
// shape_enum.rs — an enum declaration is an item, constructing a variant is an expression
use std::collections::BTreeSet;

enum Shape {
    Empty,
    Circle(f64),
    Rectangle(f64, f64),
}

fn variant_name(s: &Shape) -> &'static str {
    match s {
        Shape::Empty => "Empty",
        Shape::Circle(_) => "Circle",
        Shape::Rectangle(_, _) => "Rectangle",
    }
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Empty => 0.0,
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Rectangle(width, height) => width * height,
    }
}

fn main() {
    let shapes = vec![
        Shape::Empty,
        Shape::Circle(2.0),
        Shape::Circle(5.0),
        Shape::Rectangle(3.0, 4.0),
    ];

    println!("{:<20}: {}", "carried value count", shapes.len());
    for (i, s) in shapes.iter().enumerate() {
        println!(
            "{:<20}: {:<11} area={:.2}",
            format!("shapes[{}] variant", i),
            variant_name(s),
            area(s)
        );
    }

    let distinct_variants: BTreeSet<&str> = shapes.iter().map(variant_name).collect();
    println!("{:<20}: {}", "distinct variants", distinct_variants.len());
}
```

```
carried value count : 4
shapes[0] variant   : Empty       area=0.00
shapes[1] variant   : Circle      area=12.57
shapes[2] variant   : Circle      area=78.54
shapes[3] variant   : Rectangle   area=12.00
distinct variants   : 3
```

`shapes` was built with four separate calls, and each call was an expression: `Shape::Empty`
is a `Shape` value even though it carries no data at all; `Shape::Circle(2.0)` reaches the
same result carrying one decimal number, `Shape::Rectangle(3.0, 4.0)` reaches it carrying
two decimal numbers. All three could be `push`ed into a `Vec<Shape>` — for the compiler they
are all the same type, `Shape`.

The `variant_name` and `area` functions are used here as a tool: both use `match` to read
which variant a value carries and produce a result specific to that variant. `match`'s own
rules and the exhaustiveness of its arms are the subject of the next lesson; here it is only
visible that reaching the data from a data-carrying variant (`Circle`, `Rectangle`) requires
a route — the data-free `Empty` variant has no field to read, so the `area` function gives
it `0.0` directly.

The numbers `area` gives make this concrete: `Circle(2.0)`'s area is $\pi \times 2^2 \approx
12.57$, `Circle(5.0)`'s area is $\pi \times 5^2 \approx 78.54$. Both use the same formula —
the square of the radius times pi — but because the carried number differs, so does the
result. `Rectangle(3.0, 4.0)`'s area is `12.00`; this variant carries not one number but two,
and the `area` function reads and multiplies both. The `Empty` variant carries no number at
all, so there is no area to compute for it — `area` covers that case in its own arm with
`0.0`.

To count the variant names, an ordered set (`BTreeSet`) was used here rather than a hash
set, even though only `.len()` is read and a set's element count does not depend on
traversal order — for this single line, a hash set would give the same result. The choice
here comes not from necessity but from a habit that carries through the course: whenever a
structure whose output does not change from run to run is needed, the ordered structure is
the default choice. When this choice is required and when it is merely for consistency is
the subject of the Ordered and Set Structures lesson.

#### Variants Carrying Data and Variants Carrying None Are Counted Under the Same Type

One of `Shape`'s three variants (`Empty`) carries no data at all; two (`Circle`,
`Rectangle`) do. In the run above, `shapes.len()` counted all four; the `Shape` type holds
all of them with equal weight. This is the fundamental property that separates Rust's enum
from a list ("which option?"): it can carry not just a name but optionally a **payload**
as well. A data-free variant is not a special case of a data-carrying variant whose payload
happens to be zero — both are equally valued members of the same `enum` declaration.

This equality is also why a collection like `Vec<Shape>` is possible in the first place. A
collection carries a single type; if `Empty`, `Circle`, and `Rectangle` were three separate
types, holding all three in the same list would not be possible. `enum` solves this by
combining them under a single type: the actual variant a `Shape` variable carries is stored
in memory at runtime, but at compile time `Shape` is seen as a single type.

**DM6.** — The claim that "variants carrying data and variants carrying none are counted
under the same type" is measured here by all four being able to go into a single
`Vec<Shape>`: if the variants were counted as different types, the compiler would reject
the fourth item in `shapes` (`Rectangle`) as a different type from the first (`Empty`). The
compile succeeding cleanly is, together with the `carried value count : 4` line counting all
four, direct proof of this equality.

Variant names are not written alone but together with the type name: `Shape::Circle(2.0)`,
not `Circle(2.0)`. What requires this prefix is that the same name can be used as a variant
in more than one `enum` — if another type also had a variant named `Circle` defined, which
type's `Circle` was being referred to would be ambiguous without the `Shape::` prefix. This
is a different point from constructing a struct value (`Point { x: 3, y: 4 }`): in
struct-construction syntax the type name is already at the front and no repeated prefix is
needed, but in variant-construction syntax the type name and the variant name are two
separate names, so a `::` is placed between them.

This single-typedness is made possible by every `Shape` value knowing within itself which
variant it carries: the `variant_name` function reads exactly this piece of information;
the `area` function both reads which variant it is and, based on that, accesses different
fields. There is no such distinction in a struct, because all of a struct's fields are
always present at the same time — a `Point` value never falls into a "only has `x` right
now" state. `enum` reverses this: a value has the fields of only **one** variant at any
given time, the fields of the other variants do not exist at all for that value.

This does not mean the value `Shape::Rectangle(3.0, 4.0)` also carries a `radius` field at
the same time — that value carries only `width` and `height`, the `Circle` variant's field
is entirely separate from it. Each arm of the `match` expression in the `area` function can
only reach the fields of its own variant; inside the `Shape::Circle(radius) => ...` arm,
reaching a `width` name is not possible, because that arm only runs when the `Circle`
variant matches, and there is no `width` at that point.

This does not touch on the question of how many bytes are allocated in memory — the lesson
does not print such a number — it only measures which fields are **accessible** in which
state. Accessibility is a source-level rule expressed by `match`'s arm structure: only the
fields of the variant a given arm matches enter that arm.

#### Limiting Measurement: Variant Count Is Not State Count

`Shape` carries three variants: `Empty`, `Circle`, `Rectangle`. But "three variants" and
"three possible values" are not the same thing. `Empty` has exactly one state it can
carry — itself. `Circle`, however, carries an `f64`, and the number of values an `f64` can
take is uncountably large. The same variant, with different radii, can produce infinitely
many different **states**.

```rust
// same_variant_different_state.rs — variant count is not state count
enum Shape {
    Circle(f64),
}

fn main() {
    let small = Shape::Circle(2.0);
    let large = Shape::Circle(5.0);

    let Shape::Circle(r1) = &small;
    let Shape::Circle(r2) = &large;

    println!("{:<24}: {}", "small circle radius", r1);
    println!("{:<24}: {}", "large circle radius", r2);
    println!("{:<24}: {}", "same variant", true);
    println!("{:<24}: {}", "same state", r1 == r2);
}
```

```
small circle radius     : 2
large circle radius     : 5
same variant            : true
same state              : false
```

`small` and `large` both carry the `Circle` variant — the answer to the "same variant"
question is `true`. But they do not carry the same state: one has radius `2.0`, the other
`5.0` — the answer to the "same state" question is `false`. The "three separate variants"
count measured in the previous run does not say how many different **states** a
data-carrying variant like `Circle` can appear in.

**DM7.** — This limiting measurement is done here by constructing two separate
`Shape::Circle` values in a single code block and printing side by side that both come from
the same variant (`true`) but carry a different radius (the result of `r1 == r2` is
`false`). "Variant count" can be found by reading the `enum` declaration from the source
(three lines, three variants), while "state count" can only be found by looking at which
values are produced while the program runs; the two answer different questions.

This distinction disappears for a variant that carries no data. `Empty` has exactly one
state, because it carries no field at all — variant count and state count coincide there.
The gap opens only for data-carrying variants, and from the moment it opens, state count
becomes dependent on the type of the data carried: a variant carrying a `bool` has two
states, a variant carrying an `f64` has, in practice, an unbounded number of states.

This holds even for a single-field variant; as the field count grows, the state count grows
multiplicatively. `Rectangle(f64, f64)` carries two separate `f64`s — width and height can
vary independently of each other, so this one variant's state space is not
one-dimensional but a two-dimensional infinity. The run above only showed
`Rectangle(3.0, 4.0)`, but the declaration `Rectangle(f64, f64)` written in the source
allows for far more than that: `Rectangle(1.0, 1.0)`, `Rectangle(0.5, 100.0)`, and
countless other pairs all belong to the same variant, but none of them share the same
state.

This is why the answer to "how many states" is given not by looking at the `enum`
declaration, but by looking at the types of its fields. A data-free variant adds exactly `1`
to the state count; a variant carrying a type with a fixed number of separate values (for
example, another `enum` that itself has a limited number of variants) contributes as many
states as that type has; a variant carrying an unbounded type like `f64` or `String` makes
the state count unbounded. The three-line `enum Shape { ... }` declaration in the source
shows none of this anywhere — all three are written in the same form, as a variant name.

This distinction has a practical consequence: the correctness of code that handles a
`Shape` value is measured by the question "were all three variants handled?" — not by the
question "were all infinite states tested?" If the `area` function gives the correct result
for `Circle(2.0)`, it gives the correct result for `Circle(5.0)` too, because both are
processed in the same arm; there is no need to test every single radius separately. The
compiler's exhaustiveness check also operates exactly at the variant level, not the state
level — this boundary already decides in advance what the "exhaustiveness" concept measured
in the next lesson does and does not guarantee.

This lesson measured how many separate forms a `Shape` value can take and how those forms
are constructed in the source. But constructing a value is only one half; **reading** which
variant the constructed value carries is the other half, and this lesson used that reading
only in the background, inside two helper functions (`variant_name`, `area`). `match`
itself — which pattern forms exist, when one arm comes before another, why the compiler
requires every variant to be handled — was never opened.

#### Summary

- An `enum` declaration is an item: it produces no value when it runs, it only defines the
  variants.
- Constructing a variant (like `Shape::Circle(2.0)`) is an expression and produces a value.
- Variants carrying data and variants carrying none are counted with equal weight under the
  same `enum` type; this is what lets a single collection (like `Vec<Shape>`) hold all of
  them together.
- Variant count is a fixed number read from the `enum` declaration in the source.
- State count is different from this: a data-carrying variant can produce one, a few, or an
  uncountably large number of states, depending on the type of the data it carries.

#### Next Step

This lesson measured how many separate variants a `Shape` value can carry and how
constructing a variant is an expression; the `variant_name` and `area` functions used
`match` to tell these variants apart but never entered `match`'s own rules. The next lesson
fills exactly that gap: how is which variant a `Shape` value carries read from the source,
how many separate pattern forms can do this, and why does the compiler require every
variant to be handled?

### Pattern Matching

Source: https://academia.sh/en/courses/rust-fundamentals/pattern-matching

The same Circle(5.0) value is taken apart by three separate pattern forms — exact, binding, guarded — and match itself, as an expression, gives the same type across every arm. The limiting measurement shows that a catch-all arm provides exhaustiveness but can silently swallow a new variant.

The Enums lesson measured how many separate forms a `Shape` value can take and how those
forms are constructed in the source — but it left **reading** which variant the constructed
value carries to two helper functions, in the background. `match` itself was never opened:
which pattern forms exist, when one arm comes before another, why the compiler requires
every variant to be handled? This lesson opens exactly these three questions.

`match` is the construct in Rust that compares a value against patterns and runs the body
of the first matching arm. The Control Flow lesson already introduced `match` as an
expression — it produces a value and every arm must give the same type. This lesson deepens
that definition: with how many different **pattern forms** can the same value be caught,
and do all of these forms really fall into the same class?

Taking values apart with patterns is not itself a new concept in this course; the idea of
branching on the shape of a value has already been established. What this lesson measures
is a requirement specific to Rust's own implementation of that idea: an arm left out
prevents the source from compiling. The scope and limits of this requirement are measured
in detail below.

#### match Is an Expression, All Arms Give the Same Type

The result of a `match` expression depends on which arm matches — but the **type** of the
result does not depend on which arm matches. The compiler checks at compile time that every
arm of a `match` gives a value of the same type; if one arm gives `&str` and another gives
`i32`, the source does not compile. This rule is a direct consequence of `match` being an
expression: an expression has a single type, and that type cannot change depending on which
arm runs.

The run below takes apart a single `Shape::Circle(5.0)` value with three separate
functions — three separate pattern forms.

```rust
// three_pattern_forms.rs — match is an expression, the same value is taken apart three ways
enum Shape {
    Empty,
    Circle(f64),
    Rectangle(f64, f64),
}

fn classify_exact(s: &Shape) -> &'static str {
    match s {
        Shape::Empty => "empty",
        Shape::Circle(_) => "circle",
        Shape::Rectangle(_, _) => "rectangle",
    }
}

fn classify_binding(s: &Shape) -> String {
    match s {
        Shape::Empty => "empty".to_string(),
        Shape::Circle(radius) => format!("circle radius={}", radius),
        Shape::Rectangle(width, height) => format!("rectangle {}x{}", width, height),
    }
}

fn classify_guarded(s: &Shape) -> &'static str {
    match s {
        Shape::Circle(radius) if *radius > 3.0 => "large circle",
        Shape::Circle(_) => "small circle",
        Shape::Rectangle(width, height) if width == height => "square",
        Shape::Rectangle(_, _) => "rectangle",
        Shape::Empty => "empty",
    }
}

fn main() {
    let value = Shape::Circle(5.0);
    println!("{:<20}: {}", "exact pattern", classify_exact(&value));
    println!("{:<20}: {}", "binding pattern", classify_binding(&value));
    println!("{:<20}: {}", "guarded pattern", classify_guarded(&value));

    let small = Shape::Circle(1.0);
    println!("{:<20}: {}", "guarded (small)", classify_guarded(&small));

    let square = Shape::Rectangle(4.0, 4.0);
    println!("{:<20}: {}", "guarded (square)", classify_guarded(&square));

    let empty = Shape::Empty;
    println!("{:<20}: {}", "exact (empty)", classify_exact(&empty));
}
```

```
exact pattern       : circle
binding pattern     : circle radius=5
guarded pattern     : large circle
guarded (small)     : small circle
guarded (square)    : square
exact (empty)       : empty
```

**DM8.** — The claim "every arm of `match` gives the same type" is measured here by the
three functions' own return-type signatures: `classify_exact` and `classify_guarded`
declare in their signature that they return `&'static str`, `classify_binding` declares
that it returns `String`. Each `match` expression inside the three functions matches the
single type in its own signature; otherwise the source would not compile at all.

#### The Same Value Taken Apart by Three Pattern Forms

`classify_exact`, `classify_binding`, and `classify_guarded` all operated on the same
`value` — `Shape::Circle(5.0)` — but each caught the value with a different pattern form.

**The exact pattern** looks only at which variant matched; it never touches the carried
data. In the arm `Shape::Circle(_) => "circle"`, the underscore (`_`) means "whatever is in
this position" — whether `radius`'s value is `5.0` or `0.3` makes no difference, the arm
only checks whether the variant is `Circle`. This is exactly what the `variant_name`
function did in the previous lesson; there too, every variant was distinguished only by its
name, without ever reading the data it carried.

**The binding pattern** does not stop at matching the variant, it also binds the carried
data to a name. In the arm `Shape::Circle(radius) => ...`, the name `radius` is bound to the
`5.0` inside the matched value, and it can be used inside the arm's body, placed into a text
template. The `circle radius=5` seen in the output is exactly the result of this binding.

The difference between the binding pattern and the exact pattern is not just "whether a
name is written or not"; the bound name is a variable genuinely **usable** in the arm's
body. In the `classify_binding` function, `radius` is passed directly as an argument
filling a text template — it behaves exactly like an ordinary function parameter. The
underscore (`_`) in the exact pattern, on the other hand, is not a name; it binds to
nothing — it only says "there is a value here, but I am not interested in it."

**The guarded pattern** adds a condition (`if`) on top of a binding pattern; in Rust this is
called a match guard. The arm `Shape::Circle(radius) if *radius > 3.0 => "large circle"`
matches only when both the variant is `Circle` and the bound `radius` is greater than
`3.0`. Since `value`'s radius is `5.0`, this condition is satisfied and the result is
`"large circle"`; the `small` value (`Circle(1.0)`) does not fall into the same arm,
because the condition is not satisfied, and it falls to the arm below,
`Shape::Circle(_) => "small circle"`.

**DM9.** — The claim that the three pattern forms are all "in the same class" is measured
by all three producing a value as the body of a `match` expression: none of them was
written for a side effect, all three were directly the return value of a function. Whether
a pattern is exact, binding, or guarded does not change whether `match` itself is an
expression — all three are instances of the same `match ... { pattern => value, ... }`
form.

The square example takes this a step further: the arm `Shape::Rectangle(width, height) if
width == height => "square"` binds two fields at once and compares them to each other.
Since the `square` value is `Rectangle(4.0, 4.0)`, this condition is also satisfied. A match
guard can use not just a single bound name but several bound names at once — the binding
itself is the pattern's job, the condition itself is an ordinary logical expression. The
comparison `width == height` is not a syntax specific to `match`; it follows the same rules
as comparing any two decimal numbers in any context, it is just applied here to the two
names the pattern bound.

The arm order in the `classify_guarded` function is not arbitrary. The arm `Shape::Circle
(radius) if *radius > 3.0` is written **before** the unconditioned arm
`Shape::Circle(_) => "small circle"`; `match` arms are tried in order from top to bottom,
and the first matching arm wins. If these two arms swapped places, the pattern `Circle(_)`,
carrying no condition at all, would catch every `Circle` value — large or small — and no
value would ever reach the conditioned arm below. The same rule applies to the `Rectangle`
arms: the square check has to come before the unconditioned `Rectangle` arm, otherwise
every rectangle — square or not — would fall into the general arm.

This ordering requirement does not show up in the `classify_exact` and `classify_binding`
functions, because none of the arms there is a subset of another: `Empty`, `Circle(_)`, and
`Rectangle(_, _)` are disjoint variants, and they catch the same value no matter what order
they are written in. Ordering only becomes significant when more than one arm targets the
same variant — as with the guarded patterns.

#### Exhaustiveness: Why a Missing Arm Fails to Compile

`match` carries a constraint specific to Rust: every arm together must cover **every**
possible variant of the matched value. This is called exhaustiveness. The
`classify_exact` function above wrote three arms — `Shape::Empty`, `Shape::Circle(_)`, and
`Shape::Rectangle(_, _)` — all three variants in `Shape`'s declaration. If one of these
arms, say the line `Shape::Rectangle(_, _) => "rectangle"`, were removed from the source,
the remaining source would not compile.

This lesson does not place source that fails to compile into a code block, because source
that does not compile never enters a block in the first place — a rejected piece of writing
has no output, and therefore produces no number to verify by running it; but the reason for
the rejection can still be described here. When the compiler sees an incomplete piece of
writing like `match s { Shape::Empty => ..., Shape::Circle(_) => ... }`, it knows that `s`
can carry a `Shape::Rectangle` value at runtime — because that is a variant the `enum
Shape` declaration allows. Since none of the arms present covers that possibility, the
compiler cannot say which arm would run, and it rejects the source. This rejection ensures
that an "unexpected value" error never occurs at runtime at all: a missing arm is caught
before the program runs, during compilation.

**DM10.** — The claim that exhaustiveness is genuinely required is not measured here by a
separate compiler invocation — since source that fails to compile does not enter a code
block — but there is indirect proof: each of the three functions above covers **all** three
of `Shape`'s variants. If exhaustiveness were not required, there would be no reason to
write out all three arms in full; all three are there only because they are necessary.

The exhaustiveness check operates by looking at the type of the value the `match` in the
source matches; it does not look at what values the carried data can take. `Shape::Circle
(f64)` can carry an uncountably large number of radius values, but the compiler does not
check this infinity one by one — it only asks whether the `Circle` variant is handled in
some arm. The distinction measured in the Enums lesson — "variant count is not state
count" — holds here too: exhaustiveness is a guarantee at the variant level, not the state
level. As long as one arm writes `Shape::Circle(radius)`, that variant is considered
covered, regardless of which value `radius` carries.

#### Limiting Measurement: a Catch-All Arm Provides Exhaustiveness but Does Not Protect

There is a shortcut for satisfying the exhaustiveness rule: writing `_ => ...` as the last
arm. This arm covers all remaining variants — however many there are in the source — in a
single stroke and satisfies the compiler. But this satisfaction does not mean the arm
genuinely considers every variant **separately**; it only means none of them was left out.

```rust
// exhaustiveness_catchall.rs — a catch-all arm provides exhaustiveness but does not protect
enum OldShape {
    Circle(f64),
    Rectangle(f64, f64),
}

enum NewShape {
    Circle(f64),
    Rectangle(f64, f64),
    #[allow(dead_code)]
    Triangle(f64, f64, f64),
}

fn label_old(s: &OldShape) -> String {
    match s {
        OldShape::Circle(radius) => format!("circle r={}", radius),
        OldShape::Rectangle(width, height) => format!("rectangle {}x{}", width, height),
    }
}

fn label_new(s: &NewShape) -> String {
    match s {
        NewShape::Circle(radius) => format!("circle r={}", radius),
        NewShape::Rectangle(width, height) => format!("rectangle {}x{}", width, height),
        _ => "other".to_string(),
    }
}

fn main() {
    let old_circle = OldShape::Circle(2.0);
    let old_rectangle = OldShape::Rectangle(2.0, 2.0);
    println!("{:<20}: {}", "old: circle", label_old(&old_circle));
    println!("{:<20}: {}", "old: rectangle", label_old(&old_rectangle));

    let new_circle = NewShape::Circle(2.0);
    let new_rectangle = NewShape::Rectangle(2.0, 2.0);
    let new_triangle = NewShape::Triangle(3.0, 4.0, 5.0);
    println!("{:<20}: {}", "new: circle", label_new(&new_circle));
    println!("{:<20}: {}", "new: rectangle", label_new(&new_rectangle));
    println!("{:<20}: {}", "new: triangle", label_new(&new_triangle));
}
```

```
old: circle         : circle r=2
old: rectangle      : rectangle 2x2
new: circle         : circle r=2
new: rectangle      : rectangle 2x2
new: triangle       : other
```

`OldShape` carries two variants, and `label_old` handles both explicitly, by name — there is
no catch-all arm, because the two arms already provide exhaustiveness on their own. Then
the source moves to `NewShape`: the same two variants (`Circle`, `Rectangle`) are kept, but
a third variant (`Triangle`) is added. The `label_new` function carries the same two arms as
`label_old`, and instead of writing a third arm, adds `_ => "other".to_string()`.

This source compiles without a problem — exhaustiveness is satisfied, because `_` covers
every variant. But when a value with real data like `Triangle(3.0, 4.0, 5.0)` enters this
code, the result is only `"other"`: the triangle's three side lengths (`3.0`, `4.0`, `5.0`)
are never read anywhere, never factored into any computation. Even though `Triangle` is a
different variant from `Circle` and `Rectangle`, it receives no separate treatment at the
code level.

**DM11.** — The claim "a catch-all arm provides exhaustiveness but does not protect" is
measured here by running two enums side by side: `OldShape` and `NewShape` carry the same
two variants (`Circle`, `Rectangle`) and give the same result for the same two inputs
(`2.0` and `2.0, 2.0`) — `"circle r=2"` and `"rectangle 2x2"`. The only difference is that
`NewShape` adds a third variant and `label_new` leaves that variant to `_`. The output's
last line — `new: triangle : other` — directly shows that this new variant falls to the
catch-all without receiving treatment of its own.

This sets firm boundaries on what exhaustiveness guarantees. The compiler only answers yes
to the question "is there **one** arm for every variant"; it does not answer the question
"was every variant handled with its own logic." Adding a variant to an enum later lets a
`match` with a `_` arm pass through without a compile error — no notice comes from the
compiler for the developer to notice the new variant, because the exhaustiveness rule is
already satisfied. The situation is different for a `match` without a catch-all: the moment
a new variant is added, every `match` that does not cover that variant stops compiling and
takes the developer directly to that line.

This difference is also visible in how `label_old` and `label_new` are written.
`label_old`, because it writes both of `OldShape`'s variants by name, would stop compiling
if a third variant were added to `OldShape` — the code would directly show which line was
missing. `label_new`, because it carries a `_` arm, keeps compiling even if a fourth variant
were added to `NewShape`; the new variant, even though it resembles none of the previous
three, falls into the same `"other"` result. Both functions satisfy the exhaustiveness
rule, but only one of them puts a future change squarely in front of the developer's eyes.

These three lessons — structs, enums, pattern matching — each answered a separate question
about a data model: which fields a value carries, how many separate forms it can take, and
how those forms are read from the source. So far every measurement was made on a single
value — a single `Point`, a single `Shape`. But a real program's data is almost never made
up of a single value; a large number of values have to be held together at once.

#### Summary

- `match` is an expression; all of its arms must give a value of the same type, otherwise
  the source does not compile.
- The same value can be taken apart with an exact pattern (checking only the variant), a
  binding pattern (binding the data to a name), and a guarded pattern (adding a condition to
  the bound data); all three are instances of the same `match` structure.
- Exhaustiveness requires a `match` to cover every possible variant of the matched value; a
  variant left out makes the source fail to compile.
- A catch-all arm (`_`) provides exhaustiveness but does not protect: it silently covers a
  variant added later without handling it with its own logic.

#### Next Step

This lesson measured how a single `Shape` value is taken apart. But real programs hold not
a single value but a large number of values at once — every shape in a list, every name in
a dictionary. So far every lesson has treated a single value bound to a single variable; the
next lesson lifts that limit and carries the same question to a group of values: how are a
large number of values held at the same time, and which class do the mutating methods of
these collections (adding, removing) fall into?

### Collections

Source: https://academia.sh/en/courses/rust-fundamentals/collections

The same 'mutate' work is measured across three separate collections but falls into one of two classes: the growable array's push gives () (statement), the hash map's insert gives the old value as an Option (expression). The limiting measurement shows that old data is silently lost if this return value is not captured.

The Pattern Matching lesson measured how a single `Shape` value is taken apart; every
lesson so far has treated a single value bound to a single variable. But a program's data
is almost never made up of a single value: many fields are held at once, and they grow and
shrink. This lesson lifts that limit and carries the same question — is a piece of writing
an expression or a statement — to three structures that hold many values at once: the
growable array, the string, the hash map.

What these three structures share is that they carry methods that **mutate** themselves:
adding an item, removing an item. The question this lesson measures is this: does the same
work — "add something" — fall into three separate classes across the three structures, or
do they all behave the same? The core of the shared definition already gave a short answer
to this question: `HashSet::insert` both mutated the set and returned a `bool`, while
`Vec::push` gave only `()`. This lesson widens that one comparison to three structures and
four methods.

#### Growable Array: Adding Is a Statement, Removing Is an Expression

`Vec` is Rust's growable array type; unlike a fixed-length array, its element count can grow
and shrink at runtime. It has two basic mutating methods: adding an item to the end and
removing an item from the end. The return types of these two methods are entirely
different from each other — and this difference is the first instance of a pattern that
will keep recurring throughout the rest of this lesson: a mutating method's class is read
not from what the method **is for**, but from **which type it returns**.

```rust
// growable_array.rs — push gives () (statement), pop gives Option (expression)
fn main() {
    let mut areas: Vec<f64> = Vec::new();

    let push_result = areas.push(12.57); // Circle(2.0) area
    println!("{:<24}: {:?}", "push return value", push_result);

    areas.push(12.0); // Rectangle(3.0, 4.0) area
    areas.push(6.0); // Triangle(3.0, 4.0, 5.0) area
    println!("{:<24}: {:?}", "areas (after push)", areas);

    let popped = areas.pop();
    println!("{:<24}: {:?}", "pop return value", popped);
    println!("{:<24}: {:?}", "areas (after pop)", areas);
}
```

```
push return value       : ()
areas (after push)      : [12.57, 12.0, 6.0]
pop return value        : Some(6.0)
areas (after pop)       : [12.57, 12.0]
```

The return value of the append-to-end method is `()` — `push return value: ()` in the
output shows this. This is a **statement**: `areas` changes, but no value representing that
change comes back. The remove-from-end method, however, returns `Option<f64>` —
`Some(6.0)`, because the list's last item was `6.0`. This is an **expression**: the value
returned is the removed item itself, and it can be used in the next computation.

**DM12.** — The claim that "two methods in the same 'work' class can fall into separate
classes" is measured here with the `push`/`pop` pair: both mutate the `areas` list, both
concern a single item, but one gives `()` and the other gives `Option<f64>`. This
measurement mirrors the same contrast established in the shared definition's core
(`expression.rs`) between `HashSet::insert` and `Vec::push`: class is read not from whether
the work is "adding" or "removing," but from the type it returns.

Why does this difference exist? Adding to the end always succeeds — whether the list is
empty or full does not change the result, there is always room to add. This is why there is
no informative result to return. Removing from the end, however, may not always succeed: if
the list is empty, there is no item to remove. `Option` carries these two possibilities — an
item was there (`Some`) or it was not (`None`) — in a single return type. `push` being
"always successful" exempts it from returning a result, while `pop`'s "being able to fail"
forces it to return one.

The line `areas (after push): [12.57, 12.0, 6.0]` in the output shows the whole list; the
square brackets and comma-separated numbers are `Vec`'s own debug formatting. After three
`push` calls the list has three elements; after the `pop` call the last element (`6.0`) is
removed, leaving two elements (`[12.57, 12.0]`). How many elements the list can hold has no
upper bound written anywhere in the source — `Vec` grows as needed, and the
implementation details of that growth are not this lesson's subject.

#### A String Is a Sequence of Bytes

In Rust, a string is a sequence not of characters but of **bytes**; text is held in UTF-8
encoding, and not every character occupies the same number of bytes. A letter in the ASCII
range occupies a single byte, while letters specific to Turkish such as `ç`, `ş`, `ğ`, `ı`,
`ö`, `ü` occupy two bytes.

```rust
// string_byte_char.rs — a string is a sequence of bytes, character count is measured separately
fn main() {
    let shape_name = "üçgen";
    println!("{:<24}: {}", "byte count (len)", shape_name.len());
    println!("{:<24}: {}", "character count (chars)", shape_name.chars().count());
}
```

```
byte count (len)        : 7
character count (chars) : 5
```

`"üçgen"` (the Turkish word for triangle) is made up of five characters: `ü`, `ç`, `g`, `e`,
`n`. But the `len()` method (which gives the byte length) gives the result `7`, while
`chars().count()` (which gives the character count) gives `5`. The two-byte gap comes from
each of the letters `ü` and `ç` occupying two bytes in UTF-8; `g`, `e`, `n` are each one
byte. This is another instance of the distinction seen in earlier lessons between "a number
that can only be found by reading the source" and "a number that can only be found by
running it": how many characters the writing `"üçgen"` has can be counted by reading the
source, but how many bytes it takes up, and which letters are multi-byte, cannot be worked
out from the source without knowing that.

**DM13.** — The claim that "byte count and character count diverge" is measured here by two
different methods giving different numbers on the same string: `len()` gives `7`,
`chars().count()` gives `5`. The difference is exactly `2`, and this is explained by each of
the string's two multi-byte characters (`ü`, `ç`) contributing one extra byte to the byte
length — of the five characters, two occupy two bytes each and three occupy one byte each:
`2×2 + 3×1 = 7`.

This distinction is a reminder that a string is also a collection: the `len()` method
carries the same name as `Vec`'s `len()` and does the same thing — it gives the count of
**items** the string carries. The difference is that a string's items are bytes, not
characters. The `chars()` method takes on the job of splitting these bytes into actual
characters and counting them; this requires a second pass, because how many bytes make up
one character can change from character to character.

This is why a string cannot be accessed directly by position (like `shape_name[0]`), the
way a growable array can. In a growable array, position `0` is always one whole item; in a
string, byte position `0` can land in the **middle** of a multi-byte character —
`"üçgen"`'s first byte is not the whole `ü` character, it is only the first of its two
bytes. Position-based access not being defined for strings removes this half-a-character
problem at the source: code that wants to take individual characters from a string is
forced to go through `chars()`, and that pass follows character boundaries, not byte
boundaries.

#### Hash Map: Adding Is an Expression

A hash map (`HashMap`) is a collection that holds key-value pairs. Where a growable array
accesses an item by position (`0`, `1`, `2`, …), a hash map accesses an item by a chosen
key — a number, a string, any comparable type. This lesson uses `&str` keys: a small table
where each shape name corresponds to its own area value. The insertion method has a return
type different from the growable array's: not `()`, but `Option<V>`.

```rust
// hash_map_insert.rs — insert returns Option (expression), not printed directly
use std::collections::HashMap;

fn main() {
    let mut area_map: HashMap<&str, f64> = HashMap::new();

    let first_result = area_map.insert("circle", 12.57);
    println!("{:<24}: {:?}", "insert return (first)", first_result);

    area_map.insert("rectangle", 12.0);
    area_map.insert("triangle", 6.0);

    let mut keys: Vec<&&str> = area_map.keys().collect();
    keys.sort();
    for key in keys {
        println!("{:<24}: {}", format!("area_map[{}]", key), area_map[key]);
    }
}
```

```
insert return (first)   : None
area_map[circle]        : 12.57
area_map[rectangle]     : 12
area_map[triangle]      : 6
```

When the key `"circle"` is added for the first time, the returned value is `None` — this
key had no previous value. This differs from `Vec::push` giving `()`: `insert` is an
expression that carries information about what happened **before** the addition. If a
second addition were made to the same key, the returned value would not be `None`, but a
`Some` carrying the previous value — this is the subject of the limiting measurement.

The hash map itself is not printed directly; the keys are first gathered into a list and
sorted, then accessed one by one. A hash structure's traversal order can change from run to
run; since every number printed in this lesson has to give the same output five years from
now as well, the hash map's raw traversal order is never relied upon anywhere. The full
reason for this rule, and its counterpart — why the ordered map's traversal is the same on
every run — is the subject of the next lesson.

This step does not block the access `area_map[key]` itself: if a key is already known,
reaching its value with that key is direct and deterministic — what changes is only which
order the map's **entirety** gets walked in. This is why the loop above first gets all the
keys with `keys()`, sorts them, and only then goes back to the map while walking the sorted
list to read each key's value — never looking at the map's own internal order at any point.

**DM14.** — The claim that "`insert` is an expression and carries the previous value" is
measured here by the first insertion call's return value: it can be bound to a variable
named `first_result` and printed with `{:?}` — this binding would be meaningless for a `()`,
since `()` carries no information at all. The `None` output directly says that the key
"circle" never existed in this map before.

The double `&` sign in the line `keys: Vec<&&str>` used to sort the keys is not arbitrary:
the `area_map.keys()` method already gives a reference to each key in the map (`&str`), and
gathering these references into a list makes the list a reference to a reference
(`&&str`). This detail lets the map itself be read without ever being copied; the full
meaning of copying and references is the subject of the Ownership and the Memory Model
course — here it is only visible which type the sorting step operates on.

#### Limiting Measurement: an Uncaptured Return Value Is Silently Lost

If the old value `insert` returns is not bound to a variable, the program keeps running —
Rust does not count this as an error. But in that case, the old value is lost without ever
appearing anywhere.

```rust
// insert_data_loss.rs — insert's old value is silently dropped if not captured
use std::collections::HashMap;

fn main() {
    let mut stock: HashMap<&str, i32> = HashMap::new();
    stock.insert("circle", 5);
    stock.insert("circle", 9); // return value not captured -- old value 5 is lost here
    println!("{:<22}: {}", "uncaptured (current)", stock["circle"]);

    let old_value = stock.insert("circle", 3);
    println!("{:<22}: {:?}", "captured (old)", old_value);
    println!("{:<22}: {}", "captured (current)", stock["circle"]);
}
```

```
uncaptured (current)  : 9
captured (old)        : Some(9)
captured (current)    : 3
```

In the second line, `9` is written to the key `"circle"`; this line's return value (the
`Some(5)` carrying `5`) is not bound to any variable, and the result is computed and
immediately discarded. The rest of the program does not notice this loss: the query
`stock["circle"]` gives back `9` directly, because the map has already been updated. Only
the **old** value is lost — the number `5` can never be retrieved anywhere in the program
after this line.

The third call repeats the same operation, but this time the return value is bound to the
name `old_value`. The result is `Some(9)` — this says that `stock["circle"]` was `9`
**before** this call. The same line both updates the map and returns the value it had
before the update; the only difference is whether the caller captures this value or not.

**DM15.** — The claim that "an uncaptured return value leads to data loss" is measured here
by running two different call forms side by side: the second `insert` call (the one writing
`9`) discards its return value, and `5` is directly observed to never appear in the program
again; the third call (the one writing `3`) uses the same structure but binds its return
value to `old_value` and gets it back as `Some(9)`. Both calls invoke the same method with
the same signature; the only difference is whether the caller binds the result to a name.

This is a direct consequence of `insert` being an expression: an expression's value is
always computed, but **using** that value is not mandatory. This question never even arises
for a statement — `push` has no value to return in the first place, and nothing to lose
either. The risk of loss appears exactly in methods that **carry** a return value; this risk
can remain invisible to someone reading the code without knowing what that return value
means.

If the `"circle"` key in the `stock` example carried a count meaning — say the number of
parts in stock — the second call silently **replacing** (not adding to) the `5` with `9`
would be a genuine data loss: the previous count would be lost without ever being summed
anywhere. In this example `insert` was deliberately used in an "overwrite" sense; code that
wants to preserve the previous value for the purpose of summing it is obligated to capture
the return value. In the source, these two intentions — overwriting and summing — are
expressed with the same `insert` call; what decides the difference between them is what the
caller does with the return value.

This lesson measured the mutating methods of three separate collections and showed that all
of them fall into one of the same two patterns: a method either gives `()` (a statement,
like `push`) or gives back information about the result of the operation (an expression,
like `pop` and `insert`). The shared trait of these two collections — the growable array and
the hash map — is that even though both answer to "at which position" or "under which key"
questions, they do not give the same guarantee about **which order** the answer will come
in. In a growable array, order is the insertion order in the source, and it never changes.
In a hash map, as this lesson measured, the raw traversal order was never relied on at
all — keys were always sorted first.

#### Summary

- The growable array's append method (`push`) returns `()` and is a statement; its
  remove-from-end method (`pop`) returns `Option` and is an expression.
- A string's byte length and its character count diverge for text containing multi-byte
  characters.
- The hash map's insertion method (`insert`) is an expression; its return value carries
  either the key's previous value (`Some`) or the fact that the key is new (`None`).
- A hash map is not printed directly; its keys are gathered and sorted first.
- If `insert`'s return value is not bound to a variable, the old value it overwrites is
  silently lost; this risk exists only for methods that carry a return value.

A collection itself can also be passed through the same expression/statement/item axis:
`Vec::new()` and `HashMap::new()` calls are expressions, because they produce an empty
collection value; binding this value to a name with `let` is again a binding statement.
This lesson's real contribution comes not from the collection itself, but from the methods
that **mutate** it — because mutation is the one moment a single call can carry both a side
effect (the collection's contents change) and an optional result (the return value) at once.

#### Next Step

This lesson measured that a hash map is not printed directly, that its keys are sorted
first — but why is this necessary, and why does its counterpart, the ordered structure, not
need this sorting step at all? The next lesson opens this question: what is the guarantee
gap between an ordered map and an ordered set versus a hash map and a hash set, and what
condition does this guarantee ask to be met?

### Ordered and Set Structures

Source: https://academia.sh/en/courses/rust-fundamentals/ordered-and-set-structures

The same four keys are put into an ordered map and a hash map: the ordered map's traversal gives the same order on every run, the hash map's is never printed — this is the course's most concrete example of the data-must-not-depend-on-the-environment rule. The limiting measurement shows that the ordering guarantee depends on a condition, using the Point type from the Structs lesson.

The Collections lesson did not print the hash map directly; it first gathered the keys,
sorted them, and only then printed them. Why this step was necessary was left open in that
lesson. This lesson — the last of the topic's five data-modeling lessons — fills exactly
that gap: what is the guarantee gap between the ordered and the hash structure, and why does
this gap force a sorting step in the source?

The four lessons so far measured, in order, a value's fields, its states, how it is taken
apart, and how it is held in bulk. This lesson stands on a different axis: when a choice is
made between two structures, does that choice have an **observable** consequence? The
answer is yes — but this consequence does not appear in most of the two structures'
operations, only in one, traversal.

Rust's standard library offers two separate implementations for both maps and sets: a
hash-based one (`HashMap`, `HashSet`) and an ordered one (`BTreeMap`, `BTreeSet`). Both
support the same basic operations — insert, query, delete — and carry the same basic
names: `insert`, `contains_key` / `contains`, `len`. A piece of source, by writing
`BTreeMap` instead of `HashMap`, keeps working unchanged most of the time. The difference
between them shows up not in the result of the operations, but in the structure's
**traversal order** — and this lesson measures exactly this one difference.

#### Set Insertion: the bool Return Is the Same in Both Structures

The return type of adding an element to a set is `bool`: `true` if the element was genuinely
new, `false` if the set already carried that element. This behavior is the same for both an
ordered set and a hash set — it is a measurement independent of the ordering guarantee.

```rust
// set_insert_bool.rs — set insertion returns bool, independent of the ordering guarantee
use std::collections::{BTreeSet, HashSet};

fn main() {
    let keys = ["triangle", "circle", "rectangle", "square"];

    let mut ordered_set: BTreeSet<&str> = BTreeSet::new();
    for k in keys {
        let inserted = ordered_set.insert(k);
        println!("{:<30}: {}", format!("ordered_set.insert({})", k), inserted);
    }
    let inserted_again = ordered_set.insert("circle");
    println!("{:<30}: {}", "ordered_set.insert(circle) 2nd", inserted_again);

    println!();
    let mut hash_set: HashSet<&str> = HashSet::new();
    for k in keys {
        let inserted = hash_set.insert(k);
        println!("{:<30}: {}", format!("hash_set.insert({})", k), inserted);
    }
    let inserted_again = hash_set.insert("circle");
    println!("{:<30}: {}", "hash_set.insert(circle) 2nd", inserted_again);
}
```

```
ordered_set.insert(triangle)  : true
ordered_set.insert(circle)    : true
ordered_set.insert(rectangle) : true
ordered_set.insert(square)    : true
ordered_set.insert(circle) 2nd: false

hash_set.insert(triangle)     : true
hash_set.insert(circle)       : true
hash_set.insert(rectangle)    : true
hash_set.insert(square)       : true
hash_set.insert(circle) 2nd   : false
```

Four different keys were added to both the ordered set and the hash set; in both
structures, the first four insertions gave `true`, and re-inserting `"circle"` gave `false`.
This run never asks **in which order** the set will be traversed — it only seeks the answer
to "did this element already exist," and this question is entirely independent of a set's
internal order.

**DM16.** — The claim that "set insertion's bool return is independent of the ordering
guarantee" is measured by adding the same four keys to both `BTreeSet` and `HashSet` and
both giving back exactly the same `true`/`false` sequence. `ordered_set` and `hash_set`
differ, at the source level, only in their type names — every other line is the same; and
yet the results overlap line by line. This overlap held across five separate runs, because
`insert`'s bool return looks at whether the element is in the set, not at how the set is
laid out in memory.

#### The Ordered Map's Traversal Is the Same on Every Run

`BTreeMap` is a map that always keeps its keys ordered. This order comes not from the
insertion order but from the keys' own values: whatever order they are inserted in,
traversal always proceeds from smallest to largest.

```rust
// ordered_map_traversal.rs — the ordered map's traversal is the same on every run
use std::collections::BTreeMap;

fn main() {
    let keys = ["triangle", "circle", "rectangle", "square"];
    let mut ordered_map: BTreeMap<&str, i32> = BTreeMap::new();
    for (i, k) in keys.iter().enumerate() {
        ordered_map.insert(k, i as i32);
    }
    for (key, value) in &ordered_map {
        println!("{:<27}: {}", format!("ordered_map[{}]", key), value);
    }
}
```

```
ordered_map[circle]        : 1
ordered_map[rectangle]     : 2
ordered_map[square]        : 3
ordered_map[triangle]      : 0
```

The keys were inserted in the source in the order `"triangle"`, `"circle"`, `"rectangle"`,
`"square"` — `triangle` first, `square` last. But the order seen in the output is entirely
different: `circle`, `rectangle`, `square`, `triangle` — alphabetical order. A `for` loop
traversing a `BTreeMap` does not follow insertion order, it follows the keys' **own mutual
ordering**.

This is a guarantee entirely different from `Vec`'s behavior. As measured in the
Collections lesson, traversing a growable array always preserves insertion order — the
third item added stays in third place. `BTreeMap` does not give this guarantee; it detaches
keys entirely from insertion order and rearranges them according to their own mutual size
relationship. This rearrangement holds even for a single insertion: if a fifth key, say
`"ellipse"`, were inserted into `ordered_map`, it would not land at the end of the list —
it would land in its alphabetical place, between `"circle"` and `"rectangle"`.

**DM17.** — The claim "the ordered map's traversal is the same on every run" is not just
left unmeasured here — quite the opposite, this lesson's own code verification rests on
this exact claim: the output above is compared against a fixed text recorded from a single
run of this lesson. If `BTreeMap`'s traversal changed from run to run, this comparison
would sooner or later stop matching. The order of the four recorded lines — `circle`,
`rectangle`, `square`, `triangle` — overlaps exactly with the keys' alphabetical order; this
is indirect proof that `BTreeMap`'s actual internal ordering follows this same order.

This determinism was already an implicitly assumed rule throughout every lesson of the
course: every run had to give a result identical to the output produced at the moment the
source was written to this file. `BTreeMap` satisfies this rule on its own; whatever the
insertion order, the same set of keys always produces the same traversal order. This makes
`BTreeMap` not just "ordered," but also the only map implementation that satisfies this
course's general requirement.

#### A Hash Map Is Never Traversed Directly — the Rule Itself

A `HashMap`'s traversal order comes not from the keys' values but from the starting state of
the hash function at that particular run; this state is freshly and randomly determined at
every program start. This randomness is not a flaw but a deliberate design decision: a map
using a fixed hash function can be deliberately slowed down with maliciously chosen input —
an attacker could precompute which keys would fall into the same internal bucket.
Re-choosing the hash function's starting state randomly on every run makes this
precomputation impossible; the cost is that two separate runs of the same program can
produce a different traversal order for the same keys.

Even if the same four keys are inserted in the same order, they may be traversed in one
order in one run and another order in another run. The rule followed throughout the course
can be written right here, together with its own justification: **a hash map or hash set is
never printed directly or traversed as-is in any lesson** — because even if the printed
output can be verified in this lesson, there is no guarantee it will stay the same in the
next run.

This rule does not make the hash map unusable; it only limits **which operations** are
safe. Reading a value by a specific key, asking whether a key exists, getting the total
element count — none of these depend on traversal order, and all give the same result on
every run. The return value of `insert`, encountered in the Collections lesson, also falls
into this safe set: reading a key's previous value as `Some`/`None` has nothing to do with
which position that key holds in the map.

The one unsafe operation is traversing the map's **entirety** in raw order and using that
order as a result — walking all the pairs with a `for` loop and printing them as they come,
or taking the first or last element "by its position in traversal order." In this lesson and
the previous one, every hash structure was either queried key by key, or had its keys
sorted before being traversed; the raw traversal order was never used as a result anywhere.

```rust
// hash_map_single_key.rs — a hash map is not traversed directly, a single key is deterministic
use std::collections::HashMap;

fn main() {
    let keys = ["triangle", "circle", "rectangle", "square"];
    let mut hash_map: HashMap<&str, i32> = HashMap::new();
    for (i, k) in keys.iter().enumerate() {
        hash_map.insert(k, i as i32);
    }
    println!("{:<28}: {}", "hash_map[circle]", hash_map["circle"]);
    println!("{:<28}: {}", "contains (square)", hash_map.contains_key("square"));
    println!("{:<28}: {}", "length", hash_map.len());
}
```

```
hash_map[circle]            : 1
contains (square)           : true
length                      : 4
```

In this run, `hash_map` is never traversed anywhere; it is only queried by a single key
(`hash_map["circle"]`), asked whether a key exists (`contains_key`), and its total length is
read (`len()`). All three are results entirely independent of **which order** the map's
elements are laid out in; `"circle"` always carries the value `1`, regardless of which
position it happens to occupy in the map.

**DM18.** — The claim "a hash map's single-key query is deterministic" is measured by this
run giving exactly the same three lines across five separate runs. This is a different
measurement from the traversal test in the `BTreeMap` example: there, the whole map was
traversed and its order was verified; here, the map is never traversed at all, only
point queries are checked. Together, the two measurements draw the exact boundary of "which
operations are safe, which are not."

The practical consequence of this boundary is this: source using a `HashMap` should not be
declared "unsafe" without regard to how it uses the map. `hash_map_single_key.rs` shows
exactly `HashMap`'s most common usage pattern — reaching a value by a specific key, asking
whether a key exists — and none of this needs traversal order anywhere. What is risky is
writing code that treats a map's entire order as **meaningful**; none of the code written
in this lesson does that.

#### Limiting Measurement: the Ordering Guarantee Is Not Free

For an ordered map or an ordered set to be able to preserve order, the key type has to
satisfy a condition: it must always be possible to answer the question "which one comes
first" between two keys. This condition is called the **ordering requirement**, and not
every type satisfies it.

The `Point` type defined in the Structs lesson (`x: i32, y: i32`) does not satisfy this
condition. There is no single answer to the question "which one comes first" between two
`Point` values — should it be ordered by `x`, by `y`, or by some combination of the two?
Rust's standard library does not make this decision automatically for `Point`; source that
tries to put `Point` values into a `BTreeSet` or use them as a `BTreeMap` key does not
compile, because the ordering requirement is not satisfied. This lesson does not place such
source in a code block — source that does not compile does not enter a block — but the
rejection can be described here: the compiler says it does not know how to compare two
`Point` values.

**DM19.** — The claim that "the ordering guarantee is not free" is measured here from the
positive side: every key used in this lesson (`&str`) already satisfies the ordering
requirement — strings can be compared alphabetically, integers can be compared by
magnitude. `ordered_map_traversal.rs` running and giving the expected output is indirect
proof that this condition is satisfied for `&str`; a type like `Point`, which does not
satisfy this condition, being unable to enter the same structure shows that the condition
is **required, not optional**.

Hash structures ask for a different condition: not that the key be comparable, but that it
be convertible to a hash value. This condition is likewise not automatically satisfied for
`Point`. The two conditions do not substitute for each other — a type can satisfy one and
not the other — but they share one thing in common: neither is automatically provided on a
"blank" type; the standard library's own types (like `i32`, `&str`) already carry these
conditions, while a user-defined type has to earn them separately.

This is also a reminder of another distinction measured in the Structs lesson: even though
`Point { x: 3, y: 4 }` and `Point { y: 4, x: 3 }` were equal field by field, the `Point`
type itself did not yet carry an automatic answer to "are two values equal" — in that
lesson this comparison was done by hand, writing `d.x == e.x && d.y == e.y`, not `d == e`.
Both the ordering requirement and the hashability requirement need this equality condition
first: without knowing when two values count as "the same," neither an ordering nor a hash
value can be computed. Because `Point` does not yet automatically carry even this equality
condition, it cannot enter an ordered or a hash structure directly.

How a type earns these conditions — which keyword or declaration gets added — falls outside
this lesson's scope; only that the condition **exists** and is **not automatically
satisfied for every type** is measured here. Because the standard library's own types carry
these conditions from the start, the keys used throughout every lesson of this course —
integers, strings — never ran into this boundary anywhere; the boundary only becomes
visible when a type like `Point`, whose fields are defined by the programmer, is involved.

The shared conclusion of five lessons is this: Rust's data-modeling tools — the struct, the
enum, pattern matching, the collection, the ordered and hash structure — all answered the
same two questions from different angles. Does something written in the source produce a
value, or does it only declare a name; and is an operation's result read from its return
type, or guessed from "what it does." The second question became especially clear in this
last lesson: `insert` returning a `bool` has nothing to do with a set's ordering guarantee;
a map's traversal order, on the other hand, is the product of a condition tied to the key's
own type.

#### Summary

- Set insertion's `bool` return behaves the same in the ordered and the hash set; it is a
  measurement independent of the ordering guarantee. Because it carries a return value,
  insertion here is an **expression**.
- The ordered map's (`BTreeMap`) traversal follows the keys' own order and is the same on
  every run.
- A hash map (`HashMap`) is never printed directly or traversed in any lesson; only
  order-independent operations like a single-key query, an existence check, and length are
  used.
- The ordering guarantee depends on a condition: the key type has to satisfy the ordering
  requirement; not every type satisfies this automatically.

#### Next Step

The topic's five data-modeling lessons close here: structs, enums, pattern matching,
collections, ordered and set structures — across five lessons, the same question was asked
throughout: does something written in the source produce a value, or does it only declare a
name? None of these five lessons asked **where in the source** these types and functions
can appear — a struct declaration, a map variable, a function, which boundary is it defined
within and how is it seen from outside that boundary? The course's closing lesson opens
this question: modules and crates determine which boundary all of this appears within.

### Modules and Crates

Source: https://academia.sh/en/courses/rust-fundamentals/modules-and-crates

mod and use are both items: neither produces a value on its own, one draws a name boundary, the other shortens an existing path. The same name is called both from inside and outside the module, and pub versus non-pub cases are compared; two separate source files are compiled with a single rustc call and produce a single crate. The limiting measurement shows that visibility must be granted separately at every link of the path: even if an item is pub, it is unreachable from outside if the module wrapping it is not pub.

Those five data-modeling lessons closed having asked one question throughout: does
something written in the source produce a value? This question never once touched on
**where in the source** a type or a function appears — a struct declaration, a map
variable, a function, which boundary is it defined within and how is it seen from outside
that boundary? The Setup and the Package Manager lesson deferred this question once
already: it said that the crate root is not a single file in a real project, that the
compiler starts there and reaches other files through `mod` declarations, but it did not
show this. This lesson — the course's wrap-up — opens that deferred question: do `mod` and
`use` produce a value, or do they only draw a boundary; and is a module's boundary the same
thing as a crate's boundary?

#### `mod` Draws a Boundary, It Produces No Value Itself

Rust's Design Goals lesson already showed a module in the form `mod measure { pub fn
unit() -> i32 { 3 } }`, and that lesson's measurement core counted the `mod` declaration as
an item — but did not dwell on it. This lesson stops exactly there: the line `mod name {
... }` produces nothing when it runs. Just like the `struct` declaration in the Structs
lesson, it introduces the compiler to a name and a boundary, it is not itself evaluated.
Every item inside it — a function, a constant, another module — only bears a value once it
is **used**.

```rust
// mod_inside_outside.rs — mod is an item, the same name is called from inside and outside the module
mod measurement {
    pub fn unit() -> i32 {
        3
    }

    fn private_factor() -> i32 {
        10
    }

    pub fn scaled() -> i32 {
        unit() * private_factor()
    }
}

fn main() {
    let from_outside = measurement::unit();
    println!("{:<36}: {}", "measurement::unit() (pub, outside)", from_outside);

    let from_inside = measurement::scaled();
    println!("{:<36}: {}", "measurement::scaled() (pub, outside)", from_inside);
}
```

```
measurement::unit() (pub, outside)  : 3
measurement::scaled() (pub, outside): 30
```

**DM20.** `measurement`'s declaration never ran on any line; both outputs seen come from
the lines where `unit` and `scaled` are **called**. The name `unit` is called here from two
separate places: from inside `main` as `measurement::unit()` — from outside the module, by
the full path — and from `scaled`'s body as `unit()` — from inside the module, with no
prefix needed. The same name, the same item, produces the same value (`3`) from two
different places; what changes is only which side of the module boundary the caller stands
on.

The `private_factor` function was declared without `pub`, and it could only be called from
`scaled`'s body — that is, from inside the `measurement` module itself. This shows that
visibility in Rust is **closed by default**: an item is invisible from outside the module it
is defined in unless a single keyword — `pub` — is added. `unit` and `scaled` were
reachable from `main` because they carry this keyword; `private_factor` was not reachable
because it does not. If `measurement::private_factor()` were written inside `main`, this
line would not compile. This rejection is not placed in a code block here — for the same
reason established in the Pattern Matching lesson, source that fails to compile does not
enter a block — but the reason can be described: the compiler sees that the call is trying
to cross a module boundary through an item that does not carry `pub`, and rejects the
source.

#### A File Boundary Becomes a Module Boundary — a Crate Is a Single Compilation Unit

The Setup and the Package Manager lesson said that the crate root is always a single file
in this course, but that in a real project the compiler starts there and reaches other
files through `mod` declarations. It is shown here: the syntax `mod name;` — bodyless,
ending only in a semicolon — tells the compiler to read the file `name.rs` in the same
directory and treat that file's **entirety** as the body of the module named `name`.

```rust
// helper.rs — the entire file pulled in by a mod name is the module's body (SOURCE)
pub fn visible_outside() -> i32 {
    private_constant() + 5
}

fn private_constant() -> i32 {
    100
}
```

```rust
// mod_file_boundary.rs — mod helper; turns a file boundary into a module boundary
mod helper;

fn main() {
    let value = helper::visible_outside();
    println!("{:<34}: {}", "helper::visible_outside()", value);
}
```

```
helper::visible_outside()         : 105
```

**DM21.** There are two separate source files in this measurement — `mod_file_boundary.rs`
and `helper.rs` — but the compile command ran once and produced a single binary: two files,
one crate. The name of this single compilation unit is the **crate**: the whole formed by
starting from a single source root given to `rustc` and gathering every file reached
through `mod`, passed through a single compilation step. The `private_constant` function
inside `helper.rs` does not carry `pub`; the same rule from the previous section applies
here too — `private_constant` can only be called from inside the `helper` module itself,
that is, from `visible_outside`'s body.

A module and a crate can be confused here, because both draw a boundary, but the boundaries
they draw are on different planes. A module is a name and visibility boundary; it does not
care whether the source sits in a single file or in ten — the `measurement` module in the
previous section showed this inside a single file. A crate, on the other hand, is a
compilation and linking boundary: however many files, however many modules it is made of,
everything that passes through a single `rustc` call belongs to the same crate. A crate can
carry more than one module — `mod_file_boundary.rs`'s crate root and the `helper` module
were two parts of the same crate here — but a module is never split across more than one
crate.

#### `use` Is an Item: the Shortcut Itself Produces No Value

Once the boundary `mod` draws is established, writing a name inside that boundary with its
full path (`measurement::unit()`) every single time is not mandatory. A `use` declaration is
a name that shortens a path, and just like `mod`, it is an item: the line itself produces no
value, it only introduces a short name the following lines can use.

```rust
// use_item.rs — use is an item, a value is born only once the shortcut name is used
mod measurement {
    pub fn unit() -> i32 {
        3
    }
}

use measurement::unit;

fn main() {
    let via_shortcut = unit();
    let via_full_path = measurement::unit();
    println!("{:<31}: {}", "unit() (via use shortcut)", via_shortcut);
    println!("{:<31}: {}", "measurement::unit() (full path)", via_full_path);
    println!("{:<31}: {}", "equal", via_shortcut == via_full_path);
}
```

```
unit() (via use shortcut)      : 3
measurement::unit() (full path): 3
equal                          : true
```

**DM22.** The line `use measurement::unit;` sits before `main`, and no output is directly
tied to it — just like `mod helper;` in the previous section, it is not itself run. Both
calls — `unit()` via the shortcut, and `measurement::unit()` via the full path — produce the
same value (`3`); the `equal` line confirms this directly. `use` introduces a shortcut, not
a new computation or a new behavior: there is no difference between the value coming through
the shortcut and the value coming through the full path, because both point to the same
item, `measurement::unit`. This belongs to the same family as the previous section's
finding — a declaration adds no new computation to the source, it only adds a name the
following lines can read; `mod` does this for a boundary, `use` does the same for a path
inside an existing boundary.

#### Limiting Measurement: Visibility Must Be Granted Separately at Every Link of the Path

The two measurements so far showed visibility at a single level: an item either carried
`pub` out to the module or it did not. The real boundary is a little more fine-grained than
that: even if an item is `pub`, if the module wrapping it is not `pub`, that item cannot be
reached from outside.

```rust
// visibility_chain.rs — visibility must be granted separately at every link of the path
mod outer {
    pub mod inner {
        pub fn value() -> i32 {
            hidden() + 1
        }

        fn hidden() -> i32 {
            6
        }
    }
}

fn main() {
    let result = outer::inner::value();
    println!("{:<37}: {}", "outer::inner::value() (two pub links)", result);
}
```

```
outer::inner::value() (two pub links): 7
```

**DM23.** The call `outer::inner::value()` compiled and gave `7`, because both links in the
path — the `inner` module and the `value` function — carry `pub`; `outer` itself does not
carry `pub`, but it is already visible from outside (from this same file) because it is
defined at the crate root's own level. If `inner` had been written without `pub`, only as
`mod inner { ... }`, the call `outer::inner::value()` would not compile even though the
`value` function itself still carried `pub` — because the path would have to pass through a
module boundary that does not carry `pub`. This source, too, is not placed in a code block,
by the same not-compiling-source rule; but its result can be described: for the compiler, a
single item's visibility is not enough — the visibility of every step along the path must be
granted separately.

This is also the boundary of the course's own wrap-up. The three measurements here — the
single-level `pub`/private contrast, a file boundary turning into a module boundary, a
multi-link visibility chain — treated visibility as a binary switch (open or closed).
Rust's intermediate grades — visible only within the crate, visible only to the parent
module, and so on — were never measured in this lesson, because the measurement axis was
still the same question: does what is written in the source produce a value. The difference
between `pub` and non-`pub` written in the source produced a single binary answer; the finer
grades within that binary answer are a separate subject, outside this axis.

#### Summary

- A `mod` declaration is an item: it produces no value itself, it only introduces a name
  and a boundary; the same name can be called without a prefix from inside the module and
  by full path from outside.
- Visibility is closed by default; a single `pub` keyword opens an item to the outside of
  its module.
- The syntax `mod name;` turns a file boundary into a module boundary; several source files
  can be compiled in a single `rustc` call and form a single crate.
- `use` is an item: it introduces a name that shortens a path, it produces no value itself;
  the same item called through the shortcut and through the full path gives the same value.
- Visibility must be granted separately at every link of the path: even if an item is
  `pub`, it is unreachable from outside if a module wrapping it is not `pub`.

#### Course Wrap-Up

This course carried a single question through twelve lessons: does something written in
the source produce a value, or does it only declare a name? The first six lessons put this
question to the language's smallest units — expressions, bindings, primitive types, control
flow; the next five lessons carried the same question to the data-modeling tools — structs,
enums, pattern matching, collections, ordered and hash structures. This last lesson added,
without changing the question itself, the question of **where** in the source something
appears: which boundary is an item defined within, and how is it seen from outside that
boundary?

Each of the twelve lessons measured its own slice and wrote its own limiting measurement;
the table below gathers all twelve measurements in one place.

| Lesson | Writing measured | Class (expression · statement · item) | Limiting measurement |
|---|---|---|---|
| Rust's Design Goals | `if`/`match` picks a branch and gives a value | expression | Class is read not from the nature of the work but from the method's signature (`insert` expression, `push` statement) |
| Setup and the Package Manager | entry point, crate root, manifest | item | The manifest is not inside the source; only the output's name changes when just that file changes, with the source unchanged |
| Variables and Immutability | a `let` binding | statement | Immutability is the binding's own boundary; sharing the data another way is a separate question, the subject of the next course |
| Primitive Types | constructing a value from one of five primitive types | expression | `()` is not valueless, it is a single-valued type |
| Functions and Expression Orientation | the body's last line (tail position) | expression | A semicolon silently drops the value; the computation still happens |
| Control Flow | an `if` without `else` | expression | Its value is always `()`; the source fails to compile if its body tries to produce another type |
| Structs | a `struct` declaration | item | Position is the sole identity in a tuple struct; field order is free in a named struct |
| Enums | an `enum` declaration | item | Variant count is not state count; a data-carrying variant carries an uncountable number of states |
| Pattern Matching | a `match` expression | expression | A catch-all arm provides exhaustiveness but does not protect: it silently covers a variant added later without handling it with its own logic |
| Collections | `push` / `insert` | statement · expression | If `insert`'s return value is not bound to a variable, the old value it overwrites is silently lost |
| Ordered and Set Structures | set insertion (`insert` → `bool`) | expression | The ordering guarantee depends on a condition: the key type has to satisfy the ordering requirement |
| Modules and Crates | `mod` declaration / `use` | item | Visibility must be granted separately at every link of the path; an item is unreachable if the module wrapping it is not, even if the item itself is `pub` |

The three classes — expression, statement, item — never changed from the start of the
course to its end; what changed was only the breadth of the writing these three classes
were applied to: from a single line to a function body, from a data type's declaration to a
module's boundary. Ownership and borrowing were deferred again and again throughout this
course — a binding's immutability (the Variables and Immutability lesson), a receiver's
`&self` sign (the Structs lesson), a reference's double `&` sign (the Collections
lesson) — each time passed over by saying this was the subject of Ownership and the Memory
Model. That course
opens this deferred question: who owns a value, what does borrowing a reference mean, and
what happens when a value is separated from its owner?

