---
title: 'Concurrency and Asynchronous Rust'
source: 'https://academia.sh/en/courses/rust-concurrency'
language: en
updated: '2026-09-15T04:58:43+00:00'
license: 'CC BY-SA 4.0'
lessons: 10
outcomes:
    - 'Explains how the type system prevents data races'
    - 'Measures by running it what a job needs in order to advance'
    - 'Compares the thread and asynchronous task models'
    - 'Protects shared state with safe primitives'
---

# Concurrency and Asynchronous Rust

In a program where several jobs run at once the real question is not which job runs when but what it needs in order to advance: the course measures every construct with one question — does this job advance on its own, when polled, when something else finishes, or not at all? Threads, channels, locks, futures and executors all pass through the same frame.

## Threads

A job moved to a separate unit of execution and waited on to finish, message passing built on the transfer of ownership, which types can cross a boundary written into the type itself, shared state protected with a lock, and the guarantee a lock-free counter gives through memory ordering.

### Thread Creation

Source: https://academia.sh/en/courses/rust-concurrency/thread-creation

The course's opening lesson runs the full measurement core: of thirteen units, 5 advanced on their own, 3 when polled, 3 when something else finished; 2 never advanced at all — thread spawning and joining establish this distribution's on-its-own and when-something-else-finishes classes.

Throughout its fifteen lessons, the Traits, Generics and Error Handling course measured how
many times a body written in the source ran, and its closing said it was not dropping that
question but changing its direction. That closing sentence anticipated that the next question
would be "which thread ran it" — but that is the JVM, Concurrency and Performance course's own
question, and it was already answered there. This course asks a different question. A job,
even when it is written in the source, may need something in order to advance: a poller,
another unit finishing, or nothing at all. The one question asked throughout the course is:
**what does this job need in order to advance?**

This is the repository's fifth concurrency course. The previous four covered the same
territory with different questions: the Operating System Concepts course counted how many
distinct interleavings were possible, Python's Concurrency and Performance course measured
which steps overlapped within a tick model, the JVM, Concurrency and Performance course tracked
which thread ran a given step, and Go's Concurrency course classified how a goroutine could
stop. This course reuses none of their names: the concepts of a lock, a channel, a thread, a
data race, and a scheduler are already established and are not redefined here. Whenever a
related concept comes up, it will first be named by the course where it was established, what
was measured there will be said in one sentence, and the discussion will then return to this
course's own question.

#### Four Classes

The question has four answers, and these four names do not change throughout the course.

| Class | Definition |
|---|---|
| on its own | Once spawned, it needs no one; it advances by itself |
| when polled | It does not advance on its own; a poller advances it |
| when something else finishes | It waits for another unit to finish |
| not at all | Written in the source, but never advanced |

This four-way split should not be confused with the JVM, Concurrency and Performance course's
classification: what was measured there was **which** thread ran a step; what is measured here
is **what** a unit needs in order to advance. It should not be confused with Go's Concurrency
course either: what was measured there was how a unit **stops** (an ending); what is measured
here is how it **advances** (a start and a continuation). The Operating System Concepts
course's interleaving count and Python's Concurrency and Performance course's tick model do
not overlap with this four-way split either: both of those counted **when** a step ran, while
this course asks **what** a unit needs in order to advance — no timing information is measured
anywhere. This lesson's subject is the on-its-own and when-something-else-finishes classes:
when a thread is spawned, does it advance on its own, or does the side waiting to join it
advance when something else finishes?

#### Thread Spawning and Joining

The Operating System Concepts course established what a thread is and how the scheduler runs
it; this course does not re-explain that concept. In Rust, spawning a thread means handing off
a closure so that a separate unit of execution runs it. The spawn call returns a handle; when
join is called on that handle, the calling side waits for the thread to finish and gains access
to the value the thread returned.

The progress conditions of these two operations differ. The spawned thread itself needs no one
from the moment it starts running — it advances on its own for as long as the scheduler grants
it a share of the processor. The side that makes the join call, by contrast, cannot advance
until that thread finishes; this is the most direct example of the when-something-else-finishes
class. The same distinction holds for a thread waiting on a signal: waiting for word from a
channel is a different mechanism from calling join, but it falls into the same class, because
both wait for an event outside themselves to occur.

The join call itself returns a value: the value the spawned closure returned comes back through
the handle. This is a direct extension of the move rule established in the Ownership and the
Memory Model course — every value the closure captures is moved into that thread, and when the
closure returns the value it produced, that value is moved into the join call in turn. The
calling side cannot access that value before the join completes; if it could, it would be
trying to read a value the thread might not have produced yet. Join falling into the
when-something-else-finishes class is therefore not an implementation detail but the necessary
consequence of the ownership rule.

#### The `not at all` Class: Written in the Source, Never Ran

Two units never advanced in this run, and both stand quite plainly in the source. The first is
a closure: it is bound under the name `_never_started`, written to send a record to the log
channel, but it is never called anywhere and never handed to a thread. In Rust, a closure is
only a value until it is called — its body compiles, but it is never triggered during the run.
The second is a task: a `PausesOnce` value named `task-never-polled` is constructed, but it is
never given to the `run` function; a future, like a closure, is only a value sitting in memory
until it is polled.

What these two share is that their presence in the source is not the same as their effect on
the run. All thirteen rows in the `classes` array are written in the source; the "record
present" column in the report table says "no" for only two of them. This does not mean the
source code contains dead code — the same closure type or the same task type could have been
called or polled in a different run. What is measured is not the possibility the source
carries, but which path was chosen in this specific run.

#### The Measurement Core

The core that the rest of the course will take cross-sections of is `advance.rs`. It spawns
thirteen separate units in a single run, classifies them, and collects the records that arrive
over a channel. The records are written not into a hash structure but into an ordinary list,
and are sorted before they are read — which thread sent its record in which order is never
measured or printed anywhere in this course; only whether the record exists is measured.

```rust
// advance.rs — what a job needs in order to advance: no one, a poller, something else
// finishing, or it never advances at all.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::{channel, Sender};
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};
use std::thread;

// --- hand-written executor: the standard library has no executor ---

struct WakeSignal {
    ready: Mutex<bool>,
    condition: Condvar,
}

impl Wake for WakeSignal {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condition.notify_one();
    }
}

fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let w = Arc::new(WakeSignal {
        ready: Mutex::new(false),
        condition: Condvar::new(),
    });
    let waker: Waker = w.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut r = w.ready.lock().unwrap();
                while !*r {
                    r = w.condition.wait(r).unwrap();
                }
                *r = false;
            }
        }
    }
}

// --- a task that pauses once and then continues ---

struct PausesOnce {
    paused: bool,
    name: &'static str,
    channel: Sender<String>,
}

impl Future for PausesOnce {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.paused {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.paused = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

struct ImmediatelyReady {
    name: &'static str,
    channel: Sender<String>,
}

impl Future for ImmediatelyReady {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.channel.send(self.name.to_string()).unwrap();
        Poll::Ready(self.name)
    }
}

async fn await_inner(inner: PausesOnce, channel: Sender<String>) -> &'static str {
    let _ = inner.await;
    channel.send(String::from("task-awaiting")).unwrap();
    "awaiting"
}

fn main() {
    let (sender, receiver) = channel::<String>();

    // --- units that advance on their own: once spawned, they need no one ---
    for i in 1..=3 {
        let s = sender.clone();
        thread::spawn(move || {
            s.send(format!("thread-on-its-own-{}", i)).unwrap();
        });
    }

    // --- units waiting for something else to finish ---

    // (a) the main thread waits for a join
    let s = sender.clone();
    let handle = thread::spawn(move || {
        s.send(String::from("thread-joined")).unwrap();
    });
    handle.join().unwrap();
    sender.send(String::from("main-after-join")).unwrap();

    // (b) a thread waits for word from a channel
    let (signal_sender, signal_receiver) = channel::<()>();
    let s = sender.clone();
    let waiter = thread::spawn(move || {
        signal_receiver.recv().unwrap();
        s.send(String::from("thread-channel-waiter")).unwrap();
    });
    let s = sender.clone();
    thread::spawn(move || {
        s.send(String::from("thread-signaler")).unwrap();
        signal_sender.send(()).unwrap();
    });
    waiter.join().unwrap();

    // --- never started ---
    let s = sender.clone();
    let _never_started = move || {
        s.send(String::from("never-started")).unwrap();
    };

    // --- units that advance when polled ---
    let (_, poll_count_a) = run(PausesOnce {
        paused: false,
        name: "task-polled-a",
        channel: sender.clone(),
    });
    let (_, poll_count_b) = run(ImmediatelyReady {
        name: "task-ready-b",
        channel: sender.clone(),
    });

    // --- awaiting: does not advance until the inner one finishes ---
    let inner = PausesOnce {
        paused: false,
        name: "task-inner",
        channel: sender.clone(),
    };
    let (_, poll_count_c) = run(await_inner(inner, sender.clone()));

    // --- a task that is never polled ---
    let _never_polled = PausesOnce {
        paused: false,
        name: "task-never-polled",
        channel: sender.clone(),
    };

    // --- report: the expected number of records is read and sorted ---
    let expected = 3 + 2 + 2 + 2 + 2; // on its own 3, join 2, channel 2, polled 2, awaiting 2
    let mut records: Vec<String> = Vec::new();
    for _ in 0..expected {
        records.push(receiver.recv().unwrap());
    }
    records.sort();

    println!("recorded advances ({}):", records.len());
    for r in &records {
        println!("  {}", r);
    }

    println!();
    println!("poll counts: a={} b={} awaiting={}", poll_count_a, poll_count_b, poll_count_c);

    let classes: [(&str, &str); 13] = [
        ("thread-on-its-own-1", "on its own"),
        ("thread-on-its-own-2", "on its own"),
        ("thread-on-its-own-3", "on its own"),
        ("thread-signaler", "on its own"),
        ("thread-joined", "on its own"),
        ("main-after-join", "when something else finishes"),
        ("thread-channel-waiter", "when something else finishes"),
        ("task-awaiting", "when something else finishes"),
        ("task-polled-a", "when polled"),
        ("task-ready-b", "when polled"),
        ("task-inner", "when polled"),
        ("never-started", "not at all"),
        ("task-never-polled", "not at all"),
    ];

    println!();
    println!("{:<26}{:<30}{}", "unit", "class", "record present");
    for (name, class) in classes {
        let present = records.iter().any(|r| r == name);
        println!("{:<26}{:<30}{}", name, class, if present { "yes" } else { "no" });
    }

    println!();
    for c in ["on its own", "when polled", "when something else finishes", "not at all"] {
        let count = classes.iter().filter(|(_, k)| *k == c).count();
        println!("{:<30}{}", c, count);
    }
    println!("total units: {}", classes.len());
}
```

```
recorded advances (11):
  main-after-join
  task-awaiting
  task-inner
  task-polled-a
  task-ready-b
  thread-channel-waiter
  thread-joined
  thread-on-its-own-1
  thread-on-its-own-2
  thread-on-its-own-3
  thread-signaler

poll counts: a=2 b=1 awaiting=2

unit                      class                         record present
thread-on-its-own-1       on its own                    yes
thread-on-its-own-2       on its own                    yes
thread-on-its-own-3       on its own                    yes
thread-signaler           on its own                    yes
thread-joined             on its own                    yes
main-after-join           when something else finishes  yes
thread-channel-waiter     when something else finishes  yes
task-awaiting             when something else finishes  yes
task-polled-a             when polled                   yes
task-ready-b              when polled                   yes
task-inner                when polled                   yes
never-started             not at all                    no
task-never-polled         not at all                    no

on its own                    5
when polled                   3
when something else finishes  3
not at all                    2
total units: 13
```

**TH1.** This run is deterministic: across five separate runs the same thirteen units were
produced, in the same sorted list, with the same class distribution. **Of the thirteen units,
5 advanced on their own, 3 when polled, 3 when something else finished; 2 never advanced at
all.** This count does not change for the rest of the course; each lesson only takes its own
cross-section.

Three of the five `on its own` rows are the independent threads spawned by the loop, one is the
thread that sends the signal, and one is the thread spawned by the side waiting to join. What
these five share is that from the moment they are spawned to the moment they send their record,
they wait on no one. Two of the three `when something else finishes` rows are at the thread
level: the main thread cannot advance until the join call finishes, and the thread waiting on
the signal cannot advance until word arrives over the channel. The third, `task-awaiting`, is
not this lesson's subject — it is the outer layer of an asynchronous task and will be addressed
in the Asynchronous Programming topic. The three `when polled` rows belong to that same topic:
this lesson only notes that they, too, are present in the source and that their records appear
in the report table; it does not explain the polling mechanism itself.

#### The Limits of the Measurement

The **order** of the records is never measured anywhere in this lesson: whatever order the
three independent threads send their records in, the report first collects them, then sorts
them, and only then prints them. The reason is not a shortcoming but a deliberate boundary: the
order in which the scheduler runs the three threads depends on the environment, and this course
prints no value that depends on the environment. What is measured is whether the expected
number of records **exists** — how many arrived and which names arrived — not their order.

This boundary also shows up in the core's `expected` constant: the report expects exactly
eleven records and reads exactly that many, neither one more nor one fewer. If a thread never
sent its record, the program would wait for it forever — which is why the two units in the
`not at all` class (the `never-started` closure and the `task-never-polled` task) never touch
the report channel at all; their absence of a record is measured by leaving them out of the
`expected` count, that is, by the program never waiting for them in the first place.

This design decision itself carries a lesson: how many units a program has to wait for is not
something discovered while the program runs, but a number known in advance from the source.
`expected` adds up the three records of the three independently spawned threads, the two
records of the join pair, the two records of the channel pair, the two records of the two
polled tasks, and the two records of the awaiting task — eleven in total. This total is a
hand-computed constant, not a value learned from the run; this is the method the rest of the
course will use: the expected number is first derived from the source, and only then is it
checked whether the run meets that number.

#### Summary

- The course's measurement axis is what a unit needs in order to advance; four classes do not
  change throughout the course: on its own, when polled, when something else finishes, not at
  all.
- The measurement core classifies thirteen units: 5 advanced on their own, 3 when polled, 3
  when something else finished, 2 never advanced at all; this count does not change throughout
  the course.
- A spawned thread advances on its own from the moment it starts running; the side waiting to
  join it advances when something else finishes.
- The order in which records are sent is neither measured nor printed; what is measured is
  whether the expected number of records exists.
- The JVM, Concurrency and Performance course measured which thread ran a given step; this
  course measures what that same step needs in order to advance.

#### Next Step

In this lesson, the thread waiting on a signal advanced through a signal channel; the next
lesson runs the same mechanism with a payload. A value that arrives from a channel together
with its ownership leaves the side that sends it on its own, while it keeps the side that
receives it waiting when something else finishes — the next lesson measures this pair with a
message-passing example.

### Message Passing with Channels

Source: https://academia.sh/en/courses/rust-concurrency/message-passing-with-channels

The side waiting for word from a channel advances when something else finishes; the side that builds and sends the word advances on its own. The sent value's ownership moves into the channel and can no longer be used on the sending side.

The previous lesson used a signal channel only as a wake-up tool: the value sent was empty,
and the only thing that mattered was whether the send happened at all. This lesson runs the
same mechanism with a payload. The value passing through the channel is no longer empty; it
carries data, and the question of who that data belongs to is measured in this lesson together
with the question of progress.

The concept of a channel is not established in this course. Go's Concurrency course dealt with
channels in detail, but what was measured there was the channel used as a **stopping**
mechanism — part of the question of when and why a goroutine stops. In this lesson, the same
mechanism is measured as a **progress** tool: here a channel does not end a unit, it starts or
continues one. The channel's own machinery — whether it is buffered or unbuffered, its closing
behavior, its multiple-producer single-consumer pattern — was closed out in that course and is
not repeated here; this lesson only asks how the channel corresponds to the progress classes.

#### A Channel That Carries Ownership

The Ownership and the Memory Model course established what it means for a value to be moved:
when a value moves from one binding to another, the first binding is no longer valid, and the
value can only be reached through the new binding. Sending over a channel is an instance of
this same rule — the `send` call takes the value and moves it into the channel; the sending
side can never touch that value again.

The consequence is that the receiving side can mutate the value **without a lock**. The program
below sends a list from one thread to another; the receiving side grows the list, but it needs
no lock to do so, because it is the sole owner of the list at that moment.

This should not be confused with a situation where two threads can reach the same heap memory.
When the `list` value is constructed in the sending thread, it occupies a place on the heap;
the `send` call moves not the address of that place, but its **ownership**. When the receiving
side calls `list.push` and growth is needed, Rust's growable array type allocates a new place
on the heap and copies the old contents there — but during this operation there is no
possibility whatsoever of a second side reaching that same memory, because the sending thread
has already been joined and no longer holds a binding named `list`.

```rust
// move.rs — a value arriving from a channel is moved, so it is open to lock-free mutation.

use std::sync::mpsc::channel;
use std::thread;

fn main() {
    let (sender, receiver) = channel::<Vec<i32>>();

    let giver = thread::spawn(move || {
        let list = vec![1, 2, 3];
        sender.send(list).unwrap();
        // list can no longer be used here: ownership moved into the channel.
    });
    giver.join().unwrap();

    // --- receiving side: grows the list directly with no lock, because it is the sole owner ---
    let mut list = receiver.recv().unwrap();
    list.push(4);
    list.push(5);

    println!("length of the received list: {}", list.len());
    println!("grew without a lock, because this value is shared nowhere else");
}
```

```
length of the received list: 5
grew without a lock, because this value is shared nowhere else
```

**TH2.** The `list` variable never appears again in the source once it is handed to the `send`
call; if it did, the source would not compile, because accessing a moved value violates Rust's
ownership rule. This restriction is not run as an error in this lesson — it is only described
in prose — and next to it stands the accepted design that honors the restriction: the sending
side builds the value and sends it, the receiving side takes it over and grows it, and at no
moment do the two share a common reference. The Mutex and Read-Write Lock lesson will cover the
lock; the sharing found there is absent here, because there is nothing shared — there is only
something that fully changes hands.

This distinction is also reflected in the channel's own type: writing `channel::<Vec<i32>>()`
says that the channel will only carry values of **type** `Vec<i32>`, and that these values pass
as one complete, single ownership, not as a reference or a copy. When the receiving side takes
the value returned from the `receiver.recv()` call, it now holds the source's only copy of that
value; this is why the `list.push` call needs no concurrency primitive at all — at the moment
it grows, the growing list sits nowhere that any other binding can reach. This also explains
why a channel is a different tool from a mutex: a lock grants sequential access to a shared
value, while a channel hands the value over completely, without ever sharing it.

#### The Sender On Its Own, the Waiter When Something Else Finishes

Aside from the ownership story of the value passing through the channel, the same send also
carries a progress question. The measurement below shows this separately for both sides: the
sending side builds and sends three separate messages, waiting for nothing; the waiting side
makes a separate receive call for each message, and that call does not return until the other
side sends.

This pair is the same as the `thread-signaler` and `thread-channel-waiter` rows in the previous
lesson's measurement core — there, the signal was an empty value (`()`); here, the same
relationship is repeated with three separate messages that carry an actual payload. The
classification does not change: the side that builds the signal advances on its own both there
and here, and the side that waits for the signal advances when something else finishes both
there and here. What changes is what passes through the channel.

The measurement runs two separate channels together. The first is the
`message_sender`/`message_receiver` pair: it carries the actual payload, `Message` values, each
a struct carrying a content string and a sequence number. The second is the log channel,
familiar from the previous lesson: it is used for both sides to write their own progress to a
ledger, and this ledger is sorted before its order is measured. Keeping the two channels
separate is deliberate — if the channel that measures the actual message-passing got mixed up
with the log channel used to verify that measurement, it would become unclear which record is
part of the real work and which is only an observation.

The code shows both `log_sender.clone()` and the moving of a `Message` value side by side, and
the two are different things. The sending end of a channel is a cloneable type: the
`log_sender.clone()` call produces a second handle that can write to the same channel; the two
handles share no data, they only share access rights to the same destination. Every thread
having its own handle is what lets multiple senders write to the same channel concurrently —
this is why the `send()` call itself needs no mutex. The `Message` value, by contrast, is never
cloned: it is moved directly into the `send` call, because it must have exactly **one** owner.
A handle being duplicable and a value staying single-owned are two separate rules standing side
by side in the same program.

The `Message` struct in the measurement carries two fields: `content` is a string, `sequence`
is an integer. Sending the two together is a deliberate choice — it shows that a channel can
carry not just a single integer or a single string, but a value of arbitrary complexity under
the same ownership rule. The `sequence` field also serves the measurement itself: the
`total_sequence` the waiting side accumulates proves that all three messages arrived complete
and with correct content — not only that their count was right, but that their values were
carried correctly too.

```rust
// channel.rs — the channel waiter advances when something else finishes, the sender advances on its own.

use std::sync::mpsc::channel;
use std::thread;

struct Message {
    content: String,
    sequence: u32,
}

fn main() {
    let (log_sender, log_receiver) = channel::<String>();

    // --- giving side: builds and sends messages on its own, waiting for no one ---
    let (message_sender, message_receiver) = channel::<Message>();
    let lg = log_sender.clone();
    let giver = thread::spawn(move || {
        for sequence in 1..=3 {
            let value = Message {
                content: format!("record-{}", sequence),
                sequence,
            };
            message_sender.send(value).unwrap();
            lg.send(format!("giver-sent-{}", sequence)).unwrap();
        }
        // value no longer exists here: ownership moved into the channel with the send call.
    });

    // --- waiting side: advances when something else finishes, a separate recv() per message ---
    let lg = log_sender.clone();
    let waiter = thread::spawn(move || {
        let mut total_sequence = 0u32;
        for _ in 1..=3 {
            let message = message_receiver.recv().unwrap();
            total_sequence += message.sequence;
            lg.send(format!("waiter-received-{}", message.content)).unwrap();
        }
        total_sequence
    });

    giver.join().unwrap();
    let total = waiter.join().unwrap();

    let mut records: Vec<String> = Vec::new();
    for _ in 0..6 {
        records.push(log_receiver.recv().unwrap());
    }
    records.sort();

    println!("full scenario — accumulated sequence: {}", total);
    println!("records ({}):", records.len());
    for r in &records {
        println!("  {}", r);
    }

    // --- missing scenario: 3 givers expected, but only 2 were spawned ---
    let (log_sender2, log_receiver2) = channel::<String>();
    let (message_sender2, message_receiver2) = channel::<Message>();
    let lg2 = log_sender2.clone();
    let missing_giver = thread::spawn(move || {
        for sequence in 1..=2 {
            let value = Message {
                content: format!("missing-record-{}", sequence),
                sequence,
            };
            message_sender2.send(value).unwrap();
            lg2.send(format!("missing-giver-sent-{}", sequence)).unwrap();
        }
    });
    missing_giver.join().unwrap();
    drop(log_sender2);

    let mut missing_records: Vec<String> = Vec::new();
    while let Ok(r) = log_receiver2.recv() {
        missing_records.push(r);
    }
    missing_records.sort();

    let mut received_messages: Vec<String> = Vec::new();
    while let Ok(message) = message_receiver2.try_recv() {
        received_messages.push(message.content);
    }
    received_messages.sort();

    let expected = 3;
    println!();
    println!("missing scenario — expected: {}, arrived: {}", expected, received_messages.len());
    for r in &received_messages {
        println!("  {}", r);
    }
    println!("missing record count: {}", expected - received_messages.len());
}
```

```
full scenario — accumulated sequence: 6
records (6):
  giver-sent-1
  giver-sent-2
  giver-sent-3
  waiter-received-record-1
  waiter-received-record-2
  waiter-received-record-3

missing scenario — expected: 3, arrived: 2
  missing-record-1
  missing-record-2
missing record count: 1
```

**TH3.** In the full scenario, the `giver` thread builds and sends three messages, writing
every record without waiting for anyone else to finish — it falls into the on-its-own class.
The `waiter` thread, on the other hand, waits at each `recv` call for the other side's next
send — it falls into the when-something-else-finishes class. The accumulated sequence is
`1 + 2 + 3 = 6`; this value confirms that all three messages arrived in order and complete, and
it comes out identical across three separate runs.

The `total_sequence` value itself continues a rule from the previous lesson: the `waiter`
thread's closure returns this number, and the `waiter.join()` call carries that returned value
to the calling side. The rule established in the Thread Creation lesson holds here too — the
value the closure produces passes into the join call by the move rule of the Ownership and the
Memory Model course, and the calling side cannot read that value before the join completes.
What is worth noticing here is that this same move follows the same path for both `Message`
structs and a plain `u32` sum: the channel and the join apply the same ownership rule to
different shapes of data.

#### The Limits of the Measurement

The progress measured in this lesson has only two outcomes: either a message arrives and the
waiting side continues, or none arrives and the waiting side stays there. How many messages
arrive, or at what speed, is never measured in this course — only whether all three arrived.
The waiting side waits **indefinitely**. The `recv` call does not return until a message
arrives from the other side. If the expected number of sends never happens, no record is ever
created — and in a real program the waiting side would stay blocked forever. This lesson's
second measurement shows this situation without entering a deadlock, by computing the expected
and the actual counts separately: in the "missing scenario", three messages were expected, but
the giving side sent only two and finished. Instead of waiting for the third message forever,
the program detects that the sender and the log channel have closed
(`drop(log_sender2)`), collects the records that did arrive, and reports the missing count as
**1**.

This detection is not left to chance: the `drop(log_sender2)` call explicitly drops the last
remaining copy of the channel's sending end. The Ownership and the Memory Model course
established that dropping a value is one of the points where its life ends; here, the same
mechanism turns into a channel's **closing** signal. Rust's channel implementation ends the
receiving side's next `recv` call with an error once every sending end has been dropped — the
`while let Ok(r) = ...` form used in the program implicitly treats this error as the end of the
loop. If `log_sender2` were not dropped, the loop would assume the channel would never close
and would wait forever; here, the drop is not just memory cleanup but a signal to the receiver
that says "nothing more is coming".

This is not an example of how a real application manages its own risk of waiting indefinitely —
that question is close to the timeout mechanism covered by the Cancellation and Timeouts
lesson, and it will be addressed there separately. What is measured here is only this: the
difference between the expected count and the arrived count directly shows which record is
**missing** (the third message, in this example), not when the third message will arrive —
because it never does.

The call used to collect the remaining messages in the missing scenario also shows this
boundary. The program collects the messages **after** joining the `missing_giver` thread; at
this point everything that was going to be sent has already been sent, so there is no need for
a blocking call — whatever is in the channel can be read and collected immediately. This
ordering preserves determinism: the messages are collected in the same order and the same count
on every run, because the collection step does not depend on whether any live sender might
still send more. The third message of the expected count therefore never arrives, and the
program does not try to wait for it; it only compares the expected count with the arrived count
and prints the difference.

#### Summary

- The ownership of a value sent over a channel moves to the receiver; the sending side can
  never access that value again, and the receiving side can mutate it without needing any lock.
- The side that builds and sends the word advances on its own; the side waiting for word from
  the channel advances when something else finishes — this distinction holds separately for
  every `recv` call.
- In the full scenario, the accumulated sequence of the three messages (6) comes out identical
  across three separate runs; this is the measure of a complete match between sending and
  receiving.
- If the expected number of sends does not happen, the waiting side waits indefinitely; a
  missing record is measured by the difference between the expected and arrived counts, never
  by a waiting duration.
- The boundary is left to the Mutex and Read-Write Lock lesson: there is no shared value here,
  only a value that changes hands.

#### Next Step

This lesson showed a value's ability to cross from one thread to another with two examples:
`Vec<i32>` and `Message`. Both crossed without any trouble, but that crossing has never once
been questioned so far — as though every type could pass through a channel. In fact, this
permission is a condition written into the type itself, and not every type satisfies it. The
next lesson asks where that condition is held: which types can cross a thread boundary, which
cannot, and how this is known at compile time.

### Thread Safety in the Type System

Source: https://academia.sh/en/courses/rust-concurrency/thread-safety-in-the-type-system

Whether a value can cross a thread boundary is a condition written into the type: a design that can cross is actually carried across and its record is read, but the type permitting this does not show that a shared computation will give the same result as an independent assumption would.

The previous lesson showed a value's ability to cross from one thread to another with two
examples: a list, a message struct. Both crossed without any trouble, but that crossing itself
was never questioned — as though every type could pass through a channel or a shared container.
In fact, there are types that cannot cross, and this permission is checked not at runtime but
at **compile time**. This lesson's question is: what does a type's ability to cross the
boundary depend on, and where is that condition written in the source?

The JVM, Concurrency and Performance course established the concept of a data race: a situation
where two threads access the same memory concurrently, at least one of them writing, and the
result is undefined. This concept is not redefined here. Rust's difference is that instead of
catching this risk at runtime with a detector, it stops types that carry the risk from crossing
a thread boundary in the **first place**. Whether a value is open to a data race is known while
the source compiles, before that value is ever shared.

#### `Send` and `Sync`: A Permission Written Into the Type

The Ownership and the Memory Model course established the concepts of ownership, borrowing, and
references, but that course explained them under a single-thread assumption. A value's being
**movable** to another thread and a value's being **referenceable** from another thread are two
separate conditions, and Rust ties these two to two separate types: `Send` and `Sync`. If a
type is `Send`, a value of that type can have its ownership safely moved to another thread. If
a type is `Sync`, a reference to that type (`&T`) can be safely read from another thread — that
is, if `T` is `Sync`, then `&T` is `Send`.

These two are type names that are not translated, and for most types they are established
**automatically** by the compiler: if all of a struct's fields are `Send`, the struct itself is
`Send` too; the same rule holds for `Sync`. A programmer usually never writes these two types
at all; only when trying to move a value across a thread boundary does the compiler check
whether that value's type holds this permission. If the permission is absent, the source does
not compile — but this restriction is not run as an error in this lesson, it is only described
in prose.

This automatic derivation also explains why the previous two lessons' examples crossed without
ever being questioned. The `Vec<i32>` and the `Message` struct from the previous lesson — a
plain struct carrying a string field and an integer field — are both `Send` and `Sync`. The
reason is that both are built only from basic types and from collections the standard library
itself marks `Send`/`Sync`. For a type to **lose** one of these permissions, it has to carry a
special sharing mechanism inside it — exactly like the `Rc` and `RefCell` seen in the next
section. The compiler derives this permission by looking at the type definition; even if the
programmer never writes the word `Send` or `Sync` in the source at all, every `struct` and
`enum` declaration falls implicitly into one of these two classifications.

#### A Design That Can Cross

The Atomic Reference Counting lesson of the Ownership and the Memory Model course introduced
`Arc`, but the only thing measured in that lesson was whether the counter was atomic — the
question of crossing a boundary was explicitly left out there. This lesson measures that
boundary: `Arc`'s atomic counter is exactly the condition that makes it both `Send` and `Sync`.
If the type inside it holds these same two permissions too (`Mutex<T>` is always `Send`, and if
the `T` inside it is `Send` then `Mutex<T>` is also `Sync`), `Arc<Mutex<T>>` becomes a type that
can fully cross a thread boundary.

The program below places a struct whose fields are all `Send` into an `Arc<Mutex<...>>` and
shares it between two threads. `Arc` is both `Send` and `Sync` (as long as the type inside it
is too), and so is `Mutex` — which is why the `record` value can safely cross to both threads
through a cloned handle.

```rust
// boundary.rs — a value's ability to cross a thread boundary is a condition written into the type.

use std::sync::{Arc, Mutex};
use std::thread;

struct SharedRecord {
    label: String,
    value: i32,
}

fn main() {
    let record = Arc::new(Mutex::new(SharedRecord {
        label: String::from("creation"),
        value: 5,
    }));

    let r1 = Arc::clone(&record);
    let first = thread::spawn(move || {
        let mut content = r1.lock().unwrap();
        content.value += 1;
        content.label = String::from("first-wrote");
    });
    first.join().unwrap();

    let r2 = Arc::clone(&record);
    let second = thread::spawn(move || {
        let content = r2.lock().unwrap();
        (content.value, content.label.clone())
    });
    let (read_value, read_label) = second.join().unwrap();

    println!("crossed the boundary — value: {}, label: {}", read_value, read_label);
}
```

```
crossed the boundary — value: 6, label: first-wrote
```

**TH4.** The `first` thread is called while the lock is free, so it advances needing no one —
it falls into the on-its-own class. The `second` thread starts the same way, but its actual
dependency is that the `first.join()` call has already completed on the main flow: the program
joins the first thread before spawning the second clone, which is what ties the read value to
the guarantee that it is the value `first` wrote. The record type's ability to cross the
boundary is the precondition for these two threads being able to access the same data in
sequence; the condition itself is provided not at runtime, but at compile time, the moment
`Arc<Mutex<SharedRecord>>` is written.

The `Arc::clone` call itself is also worth noting: `r1` and `r2` are two separate handles
pointing at the same `Mutex`, but both share the same heap object. This is the same mechanism
as cloning the channel handle in the previous lesson — `Arc::clone` does not copy the data
either, it only increments the shared ownership counter. The difference is that a channel's
cloned end is a sending gate, while `Arc`'s cloned end is a direct access point to the shared
data. The `record.lock()` call comes into play at this point: `Arc` makes shared access
possible, while `Mutex` grants that access to only one side at a time. The two solve separate
problems — one solves the question "how many sides hold a reference to this data", the other
solves "who can write right now".

#### A Type That Cannot Cross: Why `Rc<RefCell<..>>` Cannot Pass the Boundary

The Ownership and the Memory Model course's Shared Ownership topic introduced the `Rc` and
`RefCell` pair in a single-threaded context: `Rc` tracks a value having more than one owner by
counting them, while `RefCell` checks the borrowing rules at runtime instead of at compile
time. When the two are used together, `Rc<RefCell<T>>` looks like a shared, mutable container,
just like `Arc<Mutex<...>>` in `boundary.rs` — but this container cannot cross a thread
boundary.

The reason lies in the counters themselves. The reference count `Rc` keeps would be open to a
data race if two threads could increment and decrement it at the same time, because that
counter is not atomic; `RefCell`'s runtime borrow counter is not atomic either, for the same
reason. The compiler does not **mark** either of these two types as `Send` — this is not a
shortcoming, it is precisely the source of preventing the data race. If a programmer tried to
hand an `Rc<RefCell<SharedRecord>>` value to `thread::spawn`, the source would not compile;
this rejection is not shown here with an error message, only its reason is explained. The
accepted counterpart was already run above: the same need is met by `Arc`, whose counters are
atomic, and by `Mutex`, which provides protection through locking. The difference between the
two is not just a name — `Arc`'s counter is updated with atomic operations, and `Mutex` grants
access in sequence; together, the two take on, in a world with more than one thread, the role
that `Rc<RefCell<..>>` takes on in a single-threaded world.

This distinction can be noticed even without using `Mutex` on its own. A type carrying only
`RefCell` (without shared ownership) loses its `Sync` status but not its `Send` status — a
`RefCell<T>` can be moved to another thread on its own, because after being moved that thread
becomes its sole owner; but it cannot be used **through a shared reference** between two
threads, because the runtime borrow counter would be left unprotected. `Rc<T>`, by contrast,
loses both, because `Rc`'s reason for existing is shared ownership itself, and that sharing is
never safe between threads. These three examples — plain `RefCell`, plain `Rc`, and the
combination of the two — point to three different rejections coming from the same root:
interior mutability, reference counting, or the sum of both; whichever it is, the condition in
the source is the same, a non-atomic counter or a non-atomic checking mechanism.

Although this rejection is not shown with a compile error, its outcome resembles a pattern
established in the Traits, Generics and Error Handling course: there, too, cases where a type
failed to satisfy a particular bound were explained through their reasoning alone, without
being put into a code block. The difference here is that the bounds measured there concerned
generics (which traits a type parameter had to satisfy), while what is measured here is the
permission to cross a thread boundary — both are examples of the compiler's decision about what
a type can do, given without ever running the code.

#### The Limits of the Measurement

A type permitting a sharing does not mean the program that uses that sharing is **correct**.
`Arc<Mutex<i32>>` prevents a data race — two threads cannot change the same integer at the same
time — but which operation applies in which order still depends on the program's own design.
The measurement below shows this directly: if each of two threads assumes it is independently
modifying a shared number, the expected total is one value; but when they operate on the same
number in sequence, the result is a different value.

This is only one of the problems two threads sharing a locked resource can run into; another
can arise when two threads each wait on a lock the other holds, and this course never runs such
a situation, because its outcome is a deadlock and it produces no deterministic output. What is
measured here is a narrower and more fundamental question: even if the lock itself works
correctly — even with no deadlock, even with no data race — does the order of two operations
working on a shared value change the result of those operations? The answer is yes, and this
draws the boundary of the guarantee the type provides.

```rust
// limiter.rs — the type permits sharing, but order dependence can give the wrong result.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let start = 5;

    // expected under the independent assumption: A doubles the value (10), B adds 10 on its
    // own (15); if the two had run separately the total would be 10 + 15 = 25.
    let independent_expected = (start * 2) + (start + 10);

    let shared = Arc::new(Mutex::new(start));

    let s1 = Arc::clone(&shared);
    let a = thread::spawn(move || {
        let mut value = s1.lock().unwrap();
        *value *= 2;
    });
    a.join().unwrap();

    let s2 = Arc::clone(&shared);
    let b = thread::spawn(move || {
        let mut value = s2.lock().unwrap();
        *value += 10;
    });
    b.join().unwrap();

    let actual_result = *shared.lock().unwrap();

    println!("expected under the independent assumption: {}", independent_expected);
    println!("actual result with the shared mutex: {}", actual_result);
    println!(
        "type permitted sharing, is the result the same as the independent assumption: {}",
        actual_result == independent_expected
    );
}
```

```
expected under the independent assumption: 25
actual result with the shared mutex: 20
type permitted sharing, is the result the same as the independent assumption: false
```

**TH5.** The expected value, 25, is computed under the assumption that the two operations run
on their own starting value, unaware of each other: `A` doubles five to get ten, `B` adds ten
to five to get fifteen, for a total of twenty-five. In the actual run, though, the two share
the **same** number, and the program joins `A` before `B`: `A` doubles five to reach ten, `B`
adds ten more to that ten to reach twenty. The result is twenty, not twenty-five — and this
difference comes out identical across three separate runs, because the join calls make the
order deterministic. The `Mutex<i32>` type permitted this sharing and did genuinely prevent the
data race; what it did not prevent is whether the programmer's assumption that "these two
operations are independent of each other" was true. Which operation takes the lock **first** is
still a question that depends on the program's design, and which one would win at random is
never measured in this lesson — only the result of this particular design, forced into a fixed
order (`A` first, then `B`), is measured.

What this measurement actually teaches is that the claims "the type is safe" and "the program
is correct" are different things. The type system around `Mutex<i32>` prevents two threads from
changing the same integer at the same time — this guarantee holds on every run and is never
broken. But **in which order** the lock is granted, which operation runs before the other,
depends entirely on the program's own design, and the type system says nothing about that. The
Mutex and Read-Write Lock lesson will take up this gap directly: when one side will wait for
the lock, and which of the progress classes that waiting falls into.

#### Summary

- `Send` says a type's ownership can be moved to another thread, `Sync` says a reference to a
  type can be read from another thread; both are checked at compile time.
- A struct whose fields are all `Send`/`Sync` can cross the boundary when placed inside
  `Arc<Mutex<...>>`; in the measurement, `first` advanced on its own, and `second` waited until
  `first`'s join completed.
- `Rc<RefCell<..>>` cannot cross the boundary, because neither of its counters is atomic; the
  accepted counterpart is provided jointly by `Arc` (an atomic counter) and `Mutex` (sequential
  access).
- A type permitting a sharing prevents a data race, but it does not guarantee that the program
  will give the correct result — an order-dependent computation can produce a result different
  from the independent assumption.
- Which operation takes the lock first is not measured in this lesson; only the deterministic
  result of one particular design forced into a fixed order is measured.

#### Next Step

In this lesson, two threads accessed the same data in sequence because the program itself
forced that order through join calls: `second` never started before `first` had completely
finished. But joining is not always a suitable tool — sometimes two threads need to run at the
same time, and only their access to the shared data at that moment needs to fall into sequence.
In that case, one thread has to wait for the other to release its lock, and that wait is a
different mechanism from joining. The next lesson measures this waiting directly: while the
lock holder advances on its own, how the side waiting for the lock advances when something else
finishes — and why a lock meant for reading carries a looser rule than one meant for writing.

### Mutex and Read-Write Lock

Source: https://academia.sh/en/courses/rust-concurrency/mutex-and-rwlock

The side holding a mutex advances on its own, the side waiting for it advances when something else finishes; this order is made deterministic with a channel handshake, and the read side of a read-write lock departs from a mutex by allowing more than one reader at the same time.

The previous lesson showed two threads operating in sequence on a shared integer, but showed
that the program itself forced that order through join calls: `second` never started before
`first` had completely finished. When these two operations need to run at the same time and
only need to fall into sequence at the moment they access the shared data, joining is not a
sufficient tool — the rest of the two threads can advance in parallel, and only the moments
they enter the critical section need to fall into sequence. This lesson measures directly how
a waiting side advances through a lock.

This lesson runs two different kinds of lock: `Mutex`, a mutex that allows only one side to
access at a time; `RwLock`, a read-write lock that handles read and write access separately.
Both offer a solution to the same basic problem — making concurrent access to shared data safe
— but they differ in the degree of sharing they permit. The lesson measures this difference
with three separate scenarios: the lock holder advancing on its own, the waiting side advancing
when something else finishes, and how the read side of the read-write lock loosens this rule.

#### The Lock Holder On Its Own, the Waiter When Something Else Finishes

The concept of a mutex is not established again here. The Operating System Concepts course
established what a lock is for; the JVM, Concurrency and Performance course measured the
identity of the thread holding a lock. This course asks a different question: what the side
**holding** the lock and the side **waiting** for the lock need in order to advance. The answer
is short: a `lock` call made while the lock is free returns needing no one — it falls into the
on-its-own class. A `lock` call made while the lock is held, on the other hand, waits until the
lock holder releases it — it falls into the when-something-else-finishes class. This has the
same structure as the `recv` call in the Message Passing with Channels lesson: both wait for an
event outside themselves to occur, and the difference is only whether the awaited event is a
send or a release.

The lock itself is represented by a handle the `lock` call returns. When this handle goes out
of scope (or is explicitly dropped), the lock is automatically released — an extension of the
drop rule established in the Ownership and the Memory Model course. In the program below, the
handle named `entry` inside the `holder` thread keeps the lock in hand until the function's
body finishes and it goes out of scope; when the lock is released depends not on an explicit
call from the programmer, but on where this handle's life ends. This is the same mechanism as
the ownership rule for a moved value seen in the previous lesson — here it is applied to a lock
handle.

This relationship has to be measured without looking at the runtime's actual timing, because
timing depends on the environment and is never printed in this course. The program below proves
with a channel handshake that the lock holder genuinely touches the lock, and that the waiting
side genuinely enters afterward: the waiting side does not even start trying before it receives
a signal that the holder has entered the lock, and the order of the records both sides write to
a shared ledger is forced by the lock itself. Without this handshake — that is, if `waiter` made
its own lock call without waiting for any signal — the measurement would still produce the
correct order, because the lock already gives an absolute order guarantee; but in that case it
would remain unclear whether `waiter`'s call was actually made while `A` held the lock, or
before that. The handshake makes clear **which moment** the measurement is proving.

#### A Deterministic Order Through a Handshake

The program below runs three separate scenarios back to back, all three isolated within the
same `main` function with their own channels. The first scenario measures a single waiter and
uses two separate signal channels: `a_entered_sender`/`a_entered_receiver` tells `waiter` that
`holder` has entered the lock; `proceed_sender`/`proceed_receiver` tells `holder` that `waiter`
has started trying. This two-way handshake pins down which side touches the lock when — the
lock itself (`log`) knows nothing of this signaling, it is protected only by the standard
`lock` call.

```rust
// lock.rs — the lock holder advances on its own, the lock waiter when something else finishes.

use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;

fn main() {
    // --- exit order is made deterministic with a handshake ---
    let log = Arc::new(Mutex::new(Vec::<String>::new()));
    let (a_entered_sender, a_entered_receiver) = channel::<()>();
    let (proceed_sender, proceed_receiver) = channel::<()>();

    let g = Arc::clone(&log);
    let holder = thread::spawn(move || {
        // on its own: the lock was free, taken without waiting for anyone
        let mut entry = g.lock().unwrap();
        entry.push(String::from("A-entered"));
        a_entered_sender.send(()).unwrap();
        proceed_receiver.recv().unwrap(); // held until B is confirmed to have started trying
        entry.push(String::from("A-exited"));
        // entry goes out of scope and is dropped here
    });

    a_entered_receiver.recv().unwrap();
    let g = Arc::clone(&log);
    let waiter = thread::spawn(move || {
        proceed_sender.send(()).unwrap();
        // when something else finishes: blocked here until A releases the lock
        let mut entry = g.lock().unwrap();
        entry.push(String::from("B-entered"));
        entry.push(String::from("B-exited"));
    });

    holder.join().unwrap();
    waiter.join().unwrap();

    println!("single-waiter order: {:?}", *log.lock().unwrap());

    // --- two waiters: which enters first is not measured, only that both entered ---
    let log2 = Arc::new(Mutex::new(Vec::<String>::new()));
    {
        let mut first = log2.lock().unwrap();
        first.push(String::from("start"));
    }

    let g = Arc::clone(&log2);
    let c = thread::spawn(move || {
        let mut entry = g.lock().unwrap();
        entry.push(String::from("C-entered"));
    });
    let g = Arc::clone(&log2);
    let d = thread::spawn(move || {
        let mut entry = g.lock().unwrap();
        entry.push(String::from("D-entered"));
    });
    c.join().unwrap();
    d.join().unwrap();

    let mut entrants: Vec<String> = log2
        .lock()
        .unwrap()
        .iter()
        .filter(|s| s.ends_with("-entered"))
        .cloned()
        .collect();
    entrants.sort();
    println!("records entered by the two waiters (order not measured): {:?}", entrants);

    // --- read-write lock: more than one reader can hold the lock at once ---
    let value = Arc::new(RwLock::new(42));
    let (r1_entered_sender, r1_entered_receiver) = channel::<()>();
    let (r2_done_sender, r2_done_receiver) = channel::<()>();
    let (release_sender, release_receiver) = channel::<()>();

    let v1 = Arc::clone(&value);
    let reader1 = thread::spawn(move || {
        let handle = v1.read().unwrap();
        r1_entered_sender.send(()).unwrap();
        release_receiver.recv().unwrap();
        *handle
    });

    r1_entered_receiver.recv().unwrap();
    let v2 = Arc::clone(&value);
    let reader2 = thread::spawn(move || {
        // this call also successfully takes the lock while R1 still holds the read lock
        let handle = v2.read().unwrap();
        let result = *handle;
        r2_done_sender.send(()).unwrap();
        result
    });

    r2_done_receiver.recv().unwrap();
    release_sender.send(()).unwrap();

    let result1 = reader1.join().unwrap();
    let result2 = reader2.join().unwrap();

    println!();
    println!("both readers held the lock at once, values read: {} and {}", result1, result2);
    println!("multiple readers were allowed: {}", result1 == result2);
}
```

```
single-waiter order: ["A-entered", "A-exited", "B-entered", "B-exited"]
records entered by the two waiters (order not measured): ["C-entered", "D-entered"]

both readers held the lock at once, values read: 42 and 42
multiple readers were allowed: true
```

**TH6.** The four-entry order in the first line comes out identical across three separate runs:
`A-entered, A-exited, B-entered, B-exited`. This order comes not from timing but from the
program's structure. The `waiter` thread never makes its own lock call before receiving the
signal (`a_entered_receiver.recv()`) that `holder` has entered the lock; `holder`, in turn,
writes its own record (`A-entered`) and sends the signal, and only **after** that waits for a
confirmation (`proceed_receiver.recv()`) that `waiter` has started trying, writing its second
record (`A-exited`) and releasing the lock only once that confirmation arrives. This three-step
handshake guarantees that `waiter`'s lock call is genuinely made while `A` still holds the lock
— `waiter` is blocked at that call, unable to advance until `holder` releases the lock. The
blocking duration is not measured, only the order of the result: `B` entries never appear
before `A`'s exit.

#### Two Waiters: Order Is Not Measured

In the second scenario, `C` and `D` wait on the same lock. Both eventually enter — the output
proves that — but **which one entered first** is never measured anywhere. The program does not
hide this uncertainty; instead it builds it into its structure: the records are sorted before
they are collected, exactly as the log channel did in the Thread Creation lesson. The
`entrants` list comes out as `["C-entered", "D-entered"]` on every run — this is the result of
alphabetical sorting, not of the actual entry order.

In this scenario, a `"start"` record is written to the log before `C` and `D` begin, and this
record does not appear in the reported list, because `entrants` filters for only the lines
ending in `-entered`. This filtering is deliberate: the exact order of the records `log2` holds
is in fact known and deterministic (`start` is always first), but the relative order between
`C-entered` and `D-entered` does not carry that certainty. The filtering separates what can be
measured from what cannot within the same data structure.

#### The Read-Write Lock: Multiple Readers Allowed

The third scenario directly shows the difference between a mutex and a read-write lock.
`reader1` sends a signal while holding the read lock; `reader2`, **after** receiving that
signal, requests its own read lock on the same value. If this were a mutex, `reader2` would be
blocked until `reader1` released the lock — and since `reader1` is waiting for `reader2` to
send a confirmation, that confirmation would never arrive, and the program would deadlock.
Since the program completes without deadlocking, `reader2`'s read-lock call must genuinely have
returned successfully while `reader1` still held the lock.

This inverts the same logic as the handshake in the first scenario. There, the handshake was
used to prove a wait: to show that `waiter` genuinely requested the lock while `holder` held
it. Here, the handshake is used to prove the **absence** of a wait: if the read calls blocked
each other, the program could never complete this handshake and would wait forever. The program
finishing normally is, on its own, proof that the read side of a read-write lock is shareable —
the most indirect of the measurement methods used in this lesson, but also the most conclusive.

**TH7.** The value the two readers read is exactly identical (`42` and `42`), because both
read the same shared number and no write occurs during the reading. This measurement shows
that the read side of a read-write lock is shareable **within itself**: two readers can hold
the lock at the same time, whereas if the same scenario were set up with a `Mutex`, the program
would deadlock, because `reader1` could never send its confirmation without `reader2` being
able to request its own lock.

The source of this difference is that a read-write lock offers two separate entry points. A
mutex carries a single call — whoever calls it is subject to the same rule. A read-write lock
carries separate calls for reading and writing: the read call accepts as many readers as
request it, as long as no writer holds the lock; the write call, by contrast, always insists on
staying **alone**, sharing with neither another writer nor a reader. Only the read side was run
in this measurement; the write call's behavior is identical to a mutex's — once a writer enters
the lock, every side waiting on that lock at that moment, reader or writer, waits for it to
finish.

#### The Limits of the Measurement

A lock protects **data**, it does not establish **order**. A mutex, or the write side of a
read-write lock, guarantees that only one side enters the critical section at a time — this is
what is measured in this lesson. But **which** of two waiters enters first is not part of the
lock's own contract; that is a detail that depends on the operating system's scheduler, the
lock's internal implementation, and the load at that moment. In the `C` and `D` example, this
was deliberately neither measured nor printed — by sorting the records before they are
collected, the question of which order actually occurred was removed entirely from the
program's output.

This boundary also clarifies the difference between a mutex and a read-write lock: the read
side of a read-write lock grants **permission** for more than one waiter to enter at once, but
that permission carries no order guarantee either — if a third reader had been added, when
that reader entered still would not be measured. The only problem a lock solves is when access
to the shared data is **safe**; it never solves the question of which side accesses it first.

This distinction is not an arbitrary choice, it is the natural boundary of the guarantee a lock
provides. A lock defines how many sides can be in the critical section at once (one for a
mutex, unlimited for the read side of a read-write lock); it does not define what determines
the **order** of entry into the critical section, because that is generally a decision left to
the operating system scheduler's own policy. No scheduler policy is named anywhere in this
course, and none will be; what is measured is only the guarantees a lock **gives** and **does
not give**.

#### Summary

- The side holding the lock advances on its own; the side waiting for the lock advances when
  something else finishes, until the lock is released. This lesson proved that relationship in
  a deterministic way with a channel handshake.
- Which of two waiting sides enters first is neither measured nor printed; sorting the records
  before they are collected removes this uncertainty from the program's output.
- The read side of a read-write lock allows more than one reader at the same time — this was
  compared against a situation where the same scenario would deadlock with a mutex.
- A lock protects access to shared data, but gives no guarantee about which side enters first;
  these two things are independent of each other.
- The JVM, Concurrency and Performance course measured which thread held a lock; this course
  measures what the side waiting for that same lock needs in order to advance.

#### Next Step

A lock stops the waiting side, because access to shared data has to be granted in sequence. In
all three scenarios seen in this lesson, one side had to wait for the lock — either because the
lock was held (`waiter`, `C`, `D`), or because a write request would have been involved (the
write side of a read-write lock). But not every sharing needs protection this strict —
especially when the shared value is as simple as a single number, and the operation performed
on it is small enough for the hardware itself to carry out in a single step. The next lesson
measures a form of sharing that no one waits for at all: a counter advancing lock-free, with
atomic operations, needing no one — and whether that gives the same result as a locked counter.

### Atomic Operations and Memory Ordering

Source: https://academia.sh/en/courses/rust-concurrency/atomics-and-memory-ordering

A lock-free counter advances without waiting, needing no one; the same total of increments is carried out both with a mutex and with atomic operations, and the two methods give exactly the same result — the difference is not in the result, but in whether a waiting side exists.

The previous lesson measured how the side waiting on a mutex advances: the lock holder on its
own, the waiter when something else finishes. This lesson's question is: does every kind of
sharing require a waiting side? The answer is no — simple forms of sharing that satisfy a
specific condition can be updated safely with no one waiting at all, through the hardware's own
guarantee. This is this topic's last lesson, and it measures lock-free progress directly.

This condition is narrow: only simple values that fit into a single machine word (integers,
booleans, pointer-width numbers) can be updated atomically. A struct carrying more than one
field, like the `SharedRecord` seen in earlier lessons, or a list that grows on the heap, like
the one in the Message Passing with Channels lesson, cannot be updated with a single atomic
operation — data of that shape still needs a lock. The lock-free progress this lesson measures
is therefore a narrow but significant special case: the shared update of a single number, a
single flag, a single pointer.

#### The Hardware's Single-Step Guarantee

The Operating System Concepts course and the JVM, Concurrency and Performance course both spoke
of hardware primitives like compare-and-swap; that concept is not established again here. An
atomic operation is a read-modify-write sequence the processor carries out in a single
indivisible step: incrementing a counter by one, even if two threads attempt it at the same
instant, never produces a result that is half-finished or overwritten. A mutex achieves the same
result, but by a different route — it makes everyone but the single lock holder wait. An atomic
operation makes no one wait at all; the hardware itself queues two simultaneous requests and
processes both without loss.

Seeing why this guarantee is necessary only requires looking at why an unprotected increment
gives the wrong result. Incrementing an integer by one is actually three separate steps: read
the current value, add one, write the result back. If two threads attempt these three steps at
the same time, unprotected, both can read the same old value, both can write their own newly
computed value — and whichever writes last **overwrites** the other's increment. The result is
that, despite two increments having taken place, the counter has advanced by only one; this is a
direct consequence of a data race. An atomic operation makes this overwrite impossible by
turning these three steps into a single indivisible unit at the hardware level — no other thread
can step in between the read, the add, and the write.

This difference has a counterpart in the measurement: of ten threads incrementing a counter
protected by a mutex, nine of them may, at any given moment, be waiting while the tenth holds
the lock — they fall into the when-something-else-finishes class. None of the ten threads
incrementing an atomic counter enter such a wait; all of them stay in the on-its-own class. The
measurement below shows this difference by comparing the **result** the two methods produce:
both produce the same total, because both run with no lost update — what differs is only how
they achieve it.

The `Send`/`Sync` framework established in the Thread Safety in the Type System lesson holds
here too: the `AtomicUsize` type is both `Send` and `Sync`, so `Arc<AtomicUsize>` can be shared
among more than one thread. This is the same pair of permissions `Arc<Mutex<usize>>` carries —
the difference is that `Mutex` carries a lock inside itself, while `AtomicUsize` carries no lock
at all, relying directly on the hardware's indivisible operations. Both are an instance of the
same permission in the type system, but the way they provide the guarantee differs.

#### Two Methods, Same Result

The program below defines two functions and runs both from `main` with the same constants: ten
threads, each incrementing five hundred times. `lock_and_count` does this with the familiar
`Arc<Mutex<usize>>` arrangement — every increment requests a handle, and the lock is released
once the handle goes out of scope. `atomic_count` performs the same job with `Arc<AtomicUsize>`;
there is no handle here, only a single call named `fetch_add`, and that call performs both the
read and the write internally, in one indivisible step. Both functions have the same return type
(`usize`), so their results can be compared directly.

```rust
// atomic.rs — a lock-free counter advances without waiting; increment order is not measured, only the total.

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;

const THREAD_COUNT: usize = 10;
const INCREMENTS: usize = 500;

fn lock_and_count() -> usize {
    let counter = Arc::new(Mutex::new(0usize));
    let mut handles = Vec::new();
    for _ in 0..THREAD_COUNT {
        let s = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            for _ in 0..INCREMENTS {
                // every increment requests the lock: a waiting thread advances when something else finishes
                let mut value = s.lock().unwrap();
                *value += 1;
            }
        }));
    }
    for t in handles {
        t.join().unwrap();
    }
    let result = *counter.lock().unwrap();
    result
}

fn atomic_count() -> usize {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = Vec::new();
    for _ in 0..THREAD_COUNT {
        let s = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            for _ in 0..INCREMENTS {
                // no thread waits on a lock: every increment advances on its own
                s.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in handles {
        t.join().unwrap();
    }
    counter.load(Ordering::SeqCst)
}

fn main() {
    let locked_result = lock_and_count();
    let atomic_result = atomic_count();
    let expected = THREAD_COUNT * INCREMENTS;

    println!("expected total: {}", expected);
    println!("locked total: {}", locked_result);
    println!("atomic total: {}", atomic_result);
    println!(
        "both methods gave the same result: {}",
        locked_result == atomic_result && atomic_result == expected
    );

    // --- two atomic counters: which increment happens first is not measured, only the final total ---
    let a = Arc::new(AtomicUsize::new(0));
    let b = Arc::new(AtomicUsize::new(0));
    let mut handles = Vec::new();
    for i in 0..THREAD_COUNT {
        let (a, b) = (Arc::clone(&a), Arc::clone(&b));
        handles.push(thread::spawn(move || {
            if i % 2 == 0 {
                a.fetch_add(1, Ordering::SeqCst);
            } else {
                b.fetch_add(1, Ordering::SeqCst);
            }
        }));
    }
    for t in handles {
        t.join().unwrap();
    }
    let pair_total = a.load(Ordering::SeqCst) + b.load(Ordering::SeqCst);
    println!();
    println!("total of the two separate atomic counters: {}", pair_total);
    println!("(which thread incremented which counter first is not printed)");
}
```

```
expected total: 5000
locked total: 5000
atomic total: 5000
both methods gave the same result: true

total of the two separate atomic counters: 10
(which thread incremented which counter first is not printed)
```

**TH8.** Ten threads, each incrementing five hundred times, increment the same counter five
thousand times in total. `lock_and_count` does this with a mutex: every increment requests a
`lock` call, and nine of the ten threads may be waiting for the lock at any given moment — a
mechanism that falls into the when-something-else-finishes class. `atomic_count` performs the
same five thousand increments with a `fetch_add` call: no thread needs a lock, all of them stay
in the on-its-own class. Both methods produce the **expected** total (5000), and this result
comes out identical across three separate runs — there is no lost-update difference between the
locked and the atomic counter.

The structure of the two functions makes this visible too: the loop body inside `lock_and_count`
takes a `Mutex` handle, changes the value, and releases the handle — these three steps repeat on
every increment. The loop body inside `atomic_count`, by contrast, only calls `fetch_add`; no
handle is taken and none is explicitly released, because the `AtomicUsize` type itself carries no
lock. Both functions using the same number of threads, the same number of increments, and being
compared against the same `expected` constant keeps the **method** as the single variable — any
difference in the result would come from being locked or not, from no other variable.

#### Order Is Not Measured, Only the Total

The program's second part uses two separate atomic counters: even-numbered threads increment
counter `a`, odd-numbered threads increment counter `b`. The order in which the ten threads run,
which one calls `fetch_add` first, is never measured or printed anywhere in this program — only
the two counters' final total (`10`) is printed. This shows that the rule established in the
Thread Creation lesson holds here too: no observation that depends on timing is printed, only
what is deterministic — in this example, how many threads are even- or odd-numbered, and
therefore what the final total will be — is printed.

The purpose of this second measurement is to show, by using two separate counters instead of
one, that the answer to the question "which thread ran first" does not **change the program's
output**. The order in which the scheduler runs the ten threads can differ on every run — since
the `THREAD_COUNT` constant is ten in this program, five of them write to counter `a` and five to
counter `b`, and the total of the two counters is always ten. This total does not change even if
the timing changes, because the `i % 2` computation depends on the thread's **number**, not on
its running **order**. This is another example showing the difference between the result of a
computation and the order of the operations that produced it.

#### The Limits of the Measurement

Being atomic does not mean being ordered. The `Ordering::SeqCst` used in this lesson guarantees
that the operation itself is indivisible — no thread ever sees a half-finished increment — but
**which** increment happens before another is never determined anywhere in this run, and cannot
be: ten threads run at the same time, and the order in which the scheduler grants them their
share of the processor depends on the environment. The only thing measured is that **none** of
the five thousand increments is lost — that the final total is exactly equal to the expected
number. The question of which thread performed which increment at which moment is never measured
anywhere, under this course's determinism rule.

Memory ordering itself — `Ordering::SeqCst`, `Ordering::Relaxed`, and the options between them —
is explained in this lesson **as a concept**, not measured, because it cannot be measured: the
difference between one ordering option and another shows up in a multi-processor hardware's own
cache-coherence behavior, and that behavior can vary from run to run, and even from call to call
within the same run. `SeqCst` is the strongest and easiest-to-reason-about option: every thread
behaves as if it sees every atomic operation in the same global order. Weaker options (like
`Relaxed`) guarantee only the indivisibility of a single variable, not the order in which
operations become visible to each other — this difference matters when a relationship needs to
be established between two different atomic variables, but it stays invisible in the
single-counter scenario used in this lesson.

Why `SeqCst` was chosen is also part of this boundary. This lesson increments a single atomic
variable (and, in the second measurement, two atomic variables independent of each other); no
scenario is built here that observes whether a read on one atomic variable appears **before or
after** a write on another. Had such a scenario been built, the chosen ordering option could have
affected the result, and that difference would have shown up only on particular processor
architectures, in particular cache states — that is, it would have been an environment-dependent
observation. Under this course's rule, no such observation is ever built; `SeqCst` was chosen
because it is the safest option, and for the one thing this lesson measures — the correctness of
the total — no ordering option would have given a different result from another.

#### Summary

- An atomic operation is a read-modify-write sequence the hardware carries out in a single
  indivisible step; this is what lets a lock-free counter advance on its own, needing no one.
- The same five thousand increments, performed with both the locked and the atomic method,
  produce exactly the same total (5000); the difference is not in the result but in a waiting
  side existing in the locked counter and not existing at all in the atomic one.
- In a locked counter, threads can enter the when-something-else-finishes class by waiting for
  the lock; in an atomic counter, no thread waits on anyone, all of them advance on their own.
- Being atomic is not being ordered: which increment happens first is never measured or printed
  in this course; what is measured is only the correctness of the final total.
- Memory ordering options are explained as a concept and not measured by running the program,
  because the difference between them depends on the hardware's cache behavior, and that
  behavior is never printed.
- Lock-free progress depends on a narrow condition: only simple values that fit into a single
  machine word can be updated atomically; a struct carrying more than one field still needs a
  lock.

#### Next Step

Throughout this topic's five lessons, the units measured fell into three of the course's four
classes: independent threads advancing on their own — the lock-free counters seen in this lesson
belong to this class too —, sides waiting for something else to finish, and units written in the
source that never advanced at all. Every unit that advanced rested on a shared assumption: once
started, a thread is carried forward by the operating system's scheduler — the programmer only
determines when it will **wait**, not the advancing itself. The fourth class, `when polled`, was
never seen in this topic.

The Asynchronous Programming topic reverses this assumption. A task written in the source stands
as a lazy value that never advances on its own, bound to no thread — unlike a thread, a task does
nothing the moment it is created. The next lesson measures this difference directly: of two tasks
built from the same source, one is handed to an executor and advances, the other is withheld and
produces no record — even though their sources are exactly identical.

## Asynchronous Programming

A task is a lazy value that never advances on its own: suspension points are written visibly in the source, the executor that advances a task stays outside the language itself, waiting on input and output is modeled as suspension, and dropping a task is how cancellation is established.

### Futures

Source: https://academia.sh/en/courses/rust-concurrency/futures

A future is a lazy value that advances only when polled, not the moment it is constructed: of two tasks built from the same source, the one handed to the executor writes a record to the ledger, while the one withheld from the executor leaves no trace.

The Threads topic measured threads that protected shared state with a lock or an atomic
operation; in both cases, once a unit was started it either advanced on its own or waited for
something else to finish. This topic carries the same question — what does a unit need in
order to advance — to a different kind of unit: the asynchronous task. At first the answer
looks familiar, on its own or when something else finishes. But Rust's asynchronous model
raises a third possibility, and this lesson builds exactly that: a task can sit written in the
source and never advance at all.

A thread is an execution unit the operating system allocates: the moment `thread::spawn` is
called, something is born with its own stack and its own scheduling claim, and it keeps
advancing even if the calling code never touches it again. An asynchronous task represents the
exact opposite — a plain data value that carries no execution context of its own. Constructing
an instance of `ImmediatelyReady` allocates no stack and claims no scheduling slot; what exists
is an ordinary struct instance holding a few fields. Seeing this difference matters for the
rest of the course: the Threads topic measured advancement as a decision made by the operating
system; this topic measures advancement as **a decision made by a piece of code in the
source**.

In Rust, an asynchronous task is a value that implements the standard library's `Future`
trait. This trait declares a single required method: `poll`. Called once, the method returns
one of two things — `Ready`, wrapping the value the task produced, if the task has finished, or
`Pending` if it has not. The `Future` trait is not conceptually unique to Rust; what sets it
apart is **who makes this decision**. The code producing the value does not advance on its
own — advancing it requires an outside call to trigger `poll`. The component making this call
is called the **executor**, and the third lesson of this topic is devoted to it directly; here
the executor is used only as a tool needed to advance a task, not as the subject of the lesson
itself.

This laziness is not an arbitrary implementation detail — it is a decision at the center of the
`Future` trait's design. The Programming Fundamentals course introduced the concept of lazy
evaluation: an expression's value is computed only when actually needed. Rust's futures apply
the same principle to asynchronous work. Constructing a future is only writing a **recipe** — a
record of which steps happen in which order. Actually running that recipe begins with a
separate, explicit decision: handing it to an executor. The distinction matters, because
otherwise constructing many futures at once would already be expensive: if each one started
running the moment it was constructed, all of them would already be halfway done before
anyone decided which task should take priority. Laziness places a boundary between
constructing and running; that same boundary explains, in the closing lesson of this course,
why a task left dropped can go on to never be polled again — a future sitting in the source
that never advanced is already in the same state as this lesson's `_never_polled`.

#### Constructing a Value Does Not Advance It

Constructing an instance of a type that implements `Future` looks no different from
constructing an ordinary struct instance: fields are filled in, the value is bound to a
variable. The difference is that nothing **runs** at the moment of construction. The
following excerpt defines the plainest possible future — one that completes the moment it is
ready — and the plainest executor needed to advance it.

```rust
// advance.rs — a slice of the core: a hand-written executor, a future ready immediately.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

pub struct ImmediatelyReady {
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for ImmediatelyReady {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.channel.send(self.name.to_string()).unwrap();
        Poll::Ready(self.name)
    }
}
```

The `run` function hand-builds a piece that is not part of Rust's standard library: a
`Waker`, a `Context`, and a loop that calls `poll` repeatedly. The executor's body is given in
this lesson only as a tool; what is actually measured is the `ImmediatelyReady` type itself.
Before `poll` is called, an instance of this type is indistinguishable from an ordinary value
sitting in memory — no field runs, nothing is sent down any channel. Only when `poll` is
called does `channel.send` fire and the value get produced.

The `run` loop repeats three steps: increment the poll counter, call `poll`, evaluate the
result. If it returns `Ready`, the loop ends and the total poll count is returned along with
the value. If it returns `Pending`, the loop waits on a condition variable for as long as the
logical flag carried by `Notifier` reads `false` — this is a real sleep for the thread, not a
busy loop, and it belongs to the same family of locking primitives introduced in the Mutex and
Read-Write Lock lesson. The `while` loop is used instead of a single `if` because a condition
variable can wake up even without ever receiving a wake call; rechecking the flag ignores this
possible spurious wakeup. The `Waker` is carried as a clone of an `Arc<Notifier>` — because a
task can return `Pending` more than once within a single run, the same `Notifier` has to be
shareable across multiple `poll` calls.

The following program constructs two tasks from the same source — the same `ImmediatelyReady`
definition. One is handed to `run`, the other is only bound to a variable and never touched
again.

```rust
// lazy.rs — two tasks from the same source: one handed to the executor, the other untouched.
mod advance;
use advance::{run, ImmediatelyReady};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    // two separate tasks constructed from the same source (the ImmediatelyReady definition)
    let polled = ImmediatelyReady {
        name: "task-polled",
        channel: sender.clone(),
    };
    let _never_polled = ImmediatelyReady {
        name: "task-never-polled",
        channel: sender.clone(),
    };

    // only one of them is handed to the executor
    let (value, poll_count) = run(polled);

    drop(sender);
    let records: Vec<String> = receiver.try_iter().collect();

    println!("returned value: {}", value);
    println!("poll count: {}", poll_count);
    println!("record count: {}", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
returned value: task-polled
poll count: 1
record count: 1
  task-polled
```

**AP1.** There is exactly one record in the ledger. The `_never_polled` variable is written in
the source, its type is `ImmediatelyReady`, its fields are filled in — but no line was printed
for it, because `poll` was never called. The only difference between the two tasks is whether
each was handed to the executor; their source code, their types, and their field values come
from the exact same identical construction. The record count stayed identical across five
separate runs: this is not a race, it is a behavior.

The topic's shared measurement core splits thirteen units into the same four classes: five
advance on their own, three when polled, three when something else finishes, two not at all.
The two units in this lesson — `task-polled` and `task-never-polled` — are one example each of
that thirteen's when-polled and not-at-all classes; the remaining classes appear in the Threads
topic and in the next lesson.

This is the first answer to the question this course asks. `task-polled` advanced **when
polled**: not on its own, but because `run` called it. `task-never-polled` advanced **not at
all** — it sat in the source and never once received a `poll`. The Traits, Generics and Error
Handling course counted how many times a body **ran** — every time, only when needed, or
never. This course asks a different question from that same triple: not how many times it
ran, but **who** advances it so that it can run. The subject that advances `task-polled` is
`run`; the `ImmediatelyReady` type itself makes no decision on this front, it only says what
to do once called.

#### A Task Also Produces a Value — It Is Not an Ordinary Call

An ordinary function call (`f(x)`) runs the moment it is called; the caller waits until the
return value comes back. `ImmediatelyReady`'s `poll` method looks similar — it delivers its
result the moment it is called — but this is not the general rule of the `Future` trait, it is
specific to `ImmediatelyReady`. The general rule is this: a future, once `poll` is called,
either finishes (`Ready`) or **says it has not finished yet** (`Pending`) and leaves itself for
the next `poll` call. `ImmediatelyReady` always finishes on the first call because it has
nothing to wait for; the future type built in the next lesson returns `Pending` at least once.
These two extremes are two different implementations of the same trait — the contract is the
same, the behavior differs.

The signature of `poll` carries this same difference:
`fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>`. `Context` is the
one channel the executor gives to the future — it carries a `Waker`, and once the future
considers itself ready for the next `poll`, it can call this `Waker` to notify the executor.
`ImmediatelyReady` never uses it because it never returns `Pending`; the next lesson builds a
future that uses this channel directly.

The `Waker` itself carries a lazy contract of its own: calling wake runs no code instantly, it
only leaves a signal for the executor saying "poll this task again." The `Notifier` type in
`run` holds this signal as a logical flag and a condition variable: once the wake call arrives,
it sets the flag to `true` and wakes the waiting loop. If a future returns `Pending` and never
makes this call, the executor waits forever — this is the future's own responsibility, not the
executor's. `ImmediatelyReady` carries no such risk because it never returns `Pending`; the
future type built in the next lesson fulfills this contract correctly by making the wake call
on every `Pending` return.

`run` places the task into a `Box::pin`. There is no reason for this in `ImmediatelyReady`
itself — this simple type causes no trouble if it moves. The reason shows up in the `async fn`
bodies built in the next lesson: the compiler turns an `async fn` into a state machine that
waits on another future inside itself, and this machine can carry references pointing into its
own fields. If such a value moves in memory, the references it carries go stale; `Pin` is a
guarantee that tells the compiler a value's address stays fixed between `poll` calls.
`ImmediatelyReady` does not need this guarantee, but `run` follows the same path for every
`Future`, because a generic function cannot know which future carries self-references and
which does not. The `async` syntax introduced in the next lesson also follows the laziness
rule: calling an asynchronous function does not run its body, it only produces a future
value — the body actually running, exactly as with `ImmediatelyReady` here, waits for that
value to be polled.

One more detail is worth noting: `run` itself is not asynchronous, it is an ordinary function
call. When the `run(polled)` line inside `main` executes, the calling thread stays in this call
until the future finishes — just like the main thread waiting for a join in the Threads topic.
Asynchrony here does not reduce the number of threads; it opens a way to advance more than one
future on the same thread, sequentially or nested. This distinction lays the groundwork for
understanding, in later lessons, how more than one task can coexist on the same executor.

#### `poll` Can Also Be Called Directly

`run` calls `poll` inside a loop, but this is not the only way to call it — `poll` is an
ordinary method, and any code holding a valid `Context` can call it. If
`polled.poll(&mut context)` had been written directly in place of `run`, the return value would
still have been `Poll::Ready("task-polled")` — because `ImmediatelyReady` finishes on the first
call, and how many times the loop runs does not matter to it. The executor's actual job is
invisible in this example: for a future that a single call satisfies, the executor is merely a
go-between. The difference shows up in the next lesson, in a future type that returns
`Pending` more than once — there, knowing when to call `poll` again shifts from hand-written
code to the executor's responsibility. This is exactly where the course's question comes back
in: what advances a task is not its own logic, but the code that calls it.

#### The Limiting Measurement

An unpolled task is not an **error**. `_never_polled` compiles, sits in memory as a variable,
and gets dropped when its scope ends — and through all of this it produces no error, no
warning. The compiler does not track whether a `Future` was ever actually polled; tracking
that is the programmer's or the executor's job. This is a different kind of silence from the
course's ban on environment-dependent data: what is missing here is not a record, it is
advancement that was never even requested. The code's own behavior shows why the "not at all"
class is not a separate error form — something written in the source can exist without leaving
a trace, for as long as it is never run.

This lesson's "not at all" example belongs to the same family as one of the two "not at all"
units in the topic's shared measurement core: one is a closure that is never invoked, the
other is a task that is never polled — both are written in the source, both compiled, neither
left a trace. The Threads topic showed the same silence when a unit was left unspawned; the
difference is that there, the responsible call was `thread::spawn` never being called, here it
is `poll` never being called. Both tie back to the same rule: being written in the source does
not mean having advanced.

#### Summary

- A future is a value that implements the `Future` trait; its one required method is `poll`,
  returning `Ready` (finished) or `Pending` (not finished yet).
- Constructing a future runs nothing — only a `poll` call advances it. This is the basis for a
  task falling into the **when-polled** class.
- Of two tasks constructed from the same source, the one withheld from the executor advanced
  **not at all**: it was written in the source, it compiled, but it produced no record.
- The subject that advances a task is the executor, not the task itself — a different axis from
  the Traits, Generics and Error Handling course's count of how many times a body ran.
- `Context` and `Waker` are the channel that lets a future tell the executor "notify me when
  I am ready"; this lesson introduced it, the next lesson puts it to use.
- A thread is an execution unit the operating system allocates; a future is a plain data
  value — it carries no execution context of its own, it advances only for as long as the
  executor polling it keeps calling it.

#### Next Step

This lesson showed the difference between constructing a task and advancing it, through
`ImmediatelyReady` — a future that never pauses, finishing on the first call. This was the
plainest possible kind of future: a single `poll` call is enough, `Pending` never comes back.
The next lesson extends the same contract to a future that pauses once: how a suspension point
becomes visible in the source, how the `async`/`await` syntax builds these points, and which
class a task waiting on another task falls into.

### `async`/`await`

Source: https://academia.sh/en/courses/rust-concurrency/async-await

Suspension points are written visibly in the source: a body that pauses once completes in two polls, a body that is ready immediately completes in one poll, and an outer body waiting on an inner future advances when something else finishes.

The previous lesson built the `ImmediatelyReady` type: a future that finishes the instant
`poll` is called and never returns `Pending`. That was the plainest possible body — one that
never pauses. This lesson extends the same contract to a body that pauses once, and introduces
the syntax that lets Rust generate such bodies instead of hand-writing them: `async` and
`await`. The question asked stays the same — what does this unit need in order to advance —
but now the answer comes with a visible mark in the source.

The previous lesson's `ImmediatelyReady`, and this lesson's `PausesOnce`, were types that
implemented the `Future` trait by hand: they wrote the `poll` method with their own hands. In
real programs, most of an asynchronous body's lines are not hand-written — they are written as
an ordinary function body marked with the `async` keyword, and the compiler automatically turns
that body into a `Future` implementation. `async` and `await` are the source-level shorthand
for hand-written `poll` logic; this lesson shows that underneath that shorthand, the same
contract — the `Ready`/`Pending` pair — is still running.

#### The Suspension Point Is Written in the Source

The previous lesson's `ImmediatelyReady` implemented the `Future` trait by hand. `PausesOnce`
implements the same trait so that it returns `Pending` exactly once: on the first call it
changes its state and wakes itself for the next `poll`, on the second call it completes.

```rust
// advance.rs — a slice of the core: the executor, a future that pauses once, a future
// that is ready immediately, and an asynchronous function awaiting an inner future.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

pub struct PausesOnce {
    pub paused: bool,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for PausesOnce {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.paused {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.paused = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub struct ImmediatelyReady {
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for ImmediatelyReady {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.channel.send(self.name.to_string()).unwrap();
        Poll::Ready(self.name)
    }
}

pub async fn await_inner(inner: PausesOnce, channel: Sender<String>) -> &'static str {
    let _ = inner.await;
    channel.send(String::from("task-awaiter")).unwrap();
    "awaiter"
}
```

`await_inner` is an `async fn`. This syntax saves it from having to hand-implement its body
as a `Future`: the compiler transforms the body, on its own, into a state machine, and that
machine implements the `Future` trait itself. The `inner.await` line inside the body is the
one visible trace of this transformation — the **suspension point**. When the compiler sees
this line, it adds a branch to the state machine saying "I can pause here": if `inner`'s
`poll` returns `Pending`, the outer body also finishes its own `poll` with `Pending` and picks
up where it left off on the next call. `.await` is not a macro call, it is an operator; its
name can appear freely outside code blocks too.

The state machine produced is conceptually close to the enum built in the Rust Fundamentals
course: one of a finite number of distinct states is active each time, and which state is
active depends on where the previous `poll` call left off. The difference is that this enum
is not hand-written — the compiler produces one suspension state for every `.await` in the
body and builds the transitions between them itself. `await_inner` has exactly one `.await`;
so the state machine the compiler produces switches between only two states: the state
waiting on `inner` and the completed state.

The function's signature — `async fn await_inner(...) -> &'static str` — looks as though the
written return type is `&'static str`, but the actual return value is this compiler-produced
state machine: a type never named in the source, carrying the `Future<Output = &'static str>`
contract. The associated type `F::Output` in the `run<F: Future>` signature covers exactly
this — `run` works purely by trusting the `Future` contract, without knowing which concrete
state machine it was given. The call `await_inner(inner, sender.clone())` itself runs nothing
either; the laziness rule established in the previous lesson holds here too — the call only
constructs the state machine's first state, and no field runs until it is handed to `run`.

#### The Number of Pauses Is Reflected in the Run

The following program runs three bodies on the same executor: `PausesOnce`, which pauses once,
`ImmediatelyReady`, which never pauses, and `await_inner`, which awaits a `PausesOnce` inside
it.

```rust
// poll-counts.rs — the poll counts of a body that pauses once, one that is ready
// immediately, and one that awaits an inner future.
mod advance;
use advance::{await_inner, run, PausesOnce, ImmediatelyReady};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    let (value_a, poll_count_a) = run(PausesOnce {
        paused: false,
        name: "task-polled-a",
        channel: sender.clone(),
    });
    let (value_b, poll_count_b) = run(ImmediatelyReady {
        name: "task-ready-b",
        channel: sender.clone(),
    });

    let inner = PausesOnce {
        paused: false,
        name: "task-inner",
        channel: sender.clone(),
    };
    let (value_c, poll_count_c) = run(await_inner(inner, sender.clone()));

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!("values: a={} b={} c={}", value_a, value_b, value_c);
    println!("poll counts: a={} b={} c={}", poll_count_a, poll_count_b, poll_count_c);
    println!();
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
values: a=task-polled-a b=task-ready-b c=awaiter
poll counts: a=2 b=1 c=2

records (4):
  task-awaiter
  task-inner
  task-polled-a
  task-ready-b
```

**AP2.** `task-polled-a` finished in two polls: the first call set `paused` to `true` and
returned `Pending`, the second call called `channel.send` and returned `Ready`. `task-ready-b`
finished in a single poll because it never paused. These two numbers show that how many times
**each** of two different bodies run on the same executor gets polled is directly derivable
from their behavior in the source — the poll count here is not an environment-dependent side
fact, it is a deterministic result coming from the body's own logic. All three numbers stayed
identical across five separate runs.

`await_inner`'s poll count was also two, but the reason is different. When this body reaches
`inner.await`, its own `poll` internally calls `inner`'s `poll`. On the first outer call,
`inner` returns `Pending`, and the outer body carries that straight up and returns `Pending`
too. On the second outer call, `inner` finishes (the `task-inner` record is written here),
the `.await` line ends, and the rest of the body (writing the `task-awaiter` record) runs
within that same call and returns `Ready`. Two polls either way, but one comes from its own
state, the other from a different future's state inside it.

#### The Number of Suspension Points Determines the Poll Count

A body is not limited to a single `.await`; it can carry more than one suspension point in a
row. The following excerpt replaces `await_inner` with a body that awaits two separate
`PausesOnce` values in sequence. `ImmediatelyReady` and `await_inner` are not needed for this
measurement, so they are left out of the excerpt.

```rust
// advance.rs — the core slice changes: a body with two consecutive suspension points is
// added; ImmediatelyReady and await_inner are not needed for this measurement.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

pub struct PausesOnce {
    pub paused: bool,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for PausesOnce {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.paused {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.paused = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub async fn await_twice(inner1: PausesOnce, inner2: PausesOnce) -> &'static str {
    let _ = inner1.await;
    let _ = inner2.await;
    "awaited-twice"
}
```

```rust
// two-pauses.rs — the poll count of a body carrying two consecutive suspension points.
mod advance;
use advance::{await_twice, run, PausesOnce};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    let inner1 = PausesOnce {
        paused: false,
        name: "task-inner-first",
        channel: sender.clone(),
    };
    let inner2 = PausesOnce {
        paused: false,
        name: "task-inner-second",
        channel: sender.clone(),
    };
    let (value, poll_count) = run(await_twice(inner1, inner2));

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!("value: {}", value);
    println!("poll count: {}", poll_count);
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
value: awaited-twice
poll count: 3
records (2):
  task-inner-first
  task-inner-second
```

**AP3.** Two suspension points correspond to three polls: the first call sets `inner1` to
`Pending`, the second call finishes `inner1` and triggers `inner2`'s first `poll` (which also
returns `Pending`, within that same outer call), the third call finishes `inner2` and runs the
rest of the body. The general rule follows from this: $n$ suspension points in a row require
$n+1$ polls in a row — the last call is always the one that completes the advancement, the
one that does not return `Pending`. This number, too, stayed identical across five separate
runs; the number of `.await`s in the source determines the poll count in the run in advance.
The `task-inner-second` record can never be written, in any run, before the `task-inner-first`
record exists — because the second suspension point's `inner2` only has its first `poll`
called once `inner1` has completed, so it is never evaluated before the first one finishes.
This order is guaranteed not because the ledger sorts alphabetically, but because the state
machine can only cross to the next branch after finishing the previous one.

#### Being Awaited Is Not the Same as Being Polled

`task-inner` — the `PausesOnce` instance inside `await_inner` — falls into the **when-polled**
class: the same type, the same behavior as `task-polled-a`; being used inside `.await` does
not change its class. `task-awaiter`, on the other hand — `await_inner`'s own completion
line — falls into the **when-something-else-finishes** class. The difference is this:
`task-inner` is polled directly, and needs nothing but being called repeatedly in order to
advance; `await_inner`, by contrast, never reaches its own final line until `inner` completes —
its advancement depends not on its own polling, but on **another task finishing**.

Go's Concurrency course measured how a goroutine stops — on its own, because what it was
waiting for arrived, because it was canceled, or never stopping at all. This lesson does not
ask that question; what is measured here is not completion, but **where it pauses and from
where it resumes**. `await_inner` never stops, it only returns `Pending` once and resumes from
the same point — and this resumption point matches, line for line, where `.await` is written.
A single line in the source announces the suspension point in the run ahead of time.

This distinction becomes clearer when set against the channel wait in the Threads topic.
There, a thread was suspended by the operating system while waiting for news from a channel;
the thread itself consumed no processor time while it waited, but this suspension was outside
the programmer's control. `await_inner`, by contrast, describes its own suspension with an
`.await` line **it wrote itself**; the executor does not even see this line, it only sees the
`Pending` that `poll` returns. The suspension decision here lives in the source itself; in the
thread case, it lives in a decision made by the runtime.

#### The Limiting Measurement

The suspension point is written in the source, but **how long it pauses is not written**. The
`inner.await` line says `await_inner` can pause there; how many `poll` calls pass before it
resumes depends on `inner`'s own logic, and this lesson prints only the **count**, not the
duration. `PausesOnce` pauses once because it was written that way; another implementation
could pause ten times, and the `.await` visible in the source would not change. The visibility
of the suspension point lets you read from the source that a suspension **exists** — not how
many times it will repeat.

This is the shape the course's ban on environment-dependent data will take again in the next
lesson's input/output example: a modeled wait makes only how many times something was polled
measurable, not the duration of a real delay.

The same limit holds for the two-consecutive-suspension-point measurement: the $n+1$ rule only
counts the **number** of suspension points, not how long each pause lasted. Real seconds could
pass between `inner1` and `inner2` in a real program, or none at all — the rule does not
change, because the rule counts **how many times** poll calls are made, not how much time
passes between them.

#### Summary

- `async fn` turns a body into a compiler-generated state machine instead of a hand-written
  `Future` implementation; `.await` marks the **suspension point** in that machine.
- A body that pauses once finishes in two polls, a body that never pauses finishes in one; the
  poll count is deterministic and does not change across five separate runs.
- A body awaiting an inner future also falls into the **when-polled** class, but that same
  body's own completion point falls into the **when-something-else-finishes** class — the two
  can coexist within a single task.
- Go's Concurrency course measured how a unit stops (completion); this lesson measures where it
  pauses and how it resumes (suspension and resumption).
- The suspension point is written visibly in the source, but how many times it will fire is
  not read from the source — it is only measured in the run. $n$ suspension points in a row
  require $n+1$ polls in a row: the last call is always the one that completes the
  advancement, the one that does not return `Pending`.
- An `async fn`'s body does not run the moment it is called — it only constructs the first
  state of the compiler-produced state machine; the laziness rule from the previous lesson
  holds here too.

#### Next Step

This lesson measured how many times a body pauses and resumes; the question not yet asked is
**who** schedules these pause-and-resume calls. `run` has, up to now, been used as a given
tool — the code called the same function every time and assumed it waited until woken. The
next lesson questions this assumption and takes `run` itself as its subject: it runs the same
body with two different executor implementations and measures that the executor adds nothing
to the task's body, deciding only **when** to poll it.

### Executors

Source: https://academia.sh/en/courses/rust-concurrency/executors

Rust's standard library has no executor; the course's own two hand-written executor implementations produce identical poll counts when they run the same task — an executor adds nothing to a task's body, it only decides when to poll it.

The previous two lessons used `run` as a given tool: a future went in, and the result and the
poll count came back out. The mechanism in between was never questioned. This lesson opens up
exactly that mechanism. Rust's standard library has no executor — `Future`, `Pin`, `Context`,
`Poll`, and `Waker` define a contract, but they make no decision at all about who calls that
contract, or when. The code filling this gap sits hand-written in the course's measurement
core, and this lesson takes it as its direct subject.

This gap is not a shortcoming of the language, it is a deliberate boundary. An executor's
right decision depends on which program is running: a program with a small number of
long-running tasks wants one kind of scheduling, a program with thousands of short tasks wants
another. Rather than impose a single policy, the language standardizes the contract (`Future`,
`Poll`, `Waker`) and leaves scheduling to a separate component. This lesson builds two
different executors and runs the same tasks through both, to show what this separation means
in the source: swapping the executor does not change a single line of the code defining the
task.

#### The Executor Is the Part Left Outside the Contract

The `Future` trait declares only the `poll` method; it says nothing anywhere about who calls
this method, in what order, or how often. This gap is deliberate: the language itself does not
impose a single scheduling policy, it leaves scheduling to a separate component — the
executor. The following excerpt defines two different executors. The first is `run`, used in
the previous lessons — an implementation that waits on a condition variable until woken. The
second is `run_loop` — an implementation that never waits, re-polling immediately as soon as
it gets `Pending` back.

```rust
// advance.rs — a slice of the core: two different executor implementations, the same task types.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

// executor 1: waits until woken
pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

pub struct EmptyNotifier;

impl Wake for EmptyNotifier {
    fn wake(self: Arc<Self>) {}
}

// executor 2: does not wait for the wake signal, immediately re-polls
pub fn run_loop<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(EmptyNotifier);
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        if let Poll::Ready(x) = task.as_mut().poll(&mut context) {
            return (x, poll_count);
        }
    }
}

pub struct PausesOnce {
    pub paused: bool,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for PausesOnce {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.paused {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.paused = true;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub struct ImmediatelyReady {
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for ImmediatelyReady {
    type Output = &'static str;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<&'static str> {
        self.channel.send(self.name.to_string()).unwrap();
        Poll::Ready(self.name)
    }
}
```

Both executors follow the same three steps: increment the poll counter, call `poll`, evaluate
the result. The only place they differ is what they do on a `Pending` return. `run` genuinely
waits on the condition variable that `Notifier` carries, and only resumes once `wake` is
called. `run_loop` uses a type named `EmptyNotifier`, whose `wake` call does nothing, and it
calls `poll` again without waiting every time it gets `Pending`. Both conform to the same
`Future` contract; both are valid executors. The difference between them is not in the task
itself, it is in **when** they choose to poll it again.

The difference between the two `Wake` implementations reflects this same choice. `Notifier`
carries a logical flag and a condition variable, genuinely delivering the `wake` call to the
waiting side — whoever calls it, `run` will see this call and wake up. `EmptyNotifier`, by
contrast, never uses `wake` at all; its body is empty, because `run_loop` already re-polls
without waiting for the wake call. The `Waker` itself is still handed to both future types —
`PausesOnce`'s `poll` calls `wake_by_ref` unconditionally, without knowing which executor's
`Wake` implementation its `Context` carries. Conforming to the contract demands exactly this
much; anything beyond that is the executor's own decision.

Both executors hold their own notifier inside an `Arc`. The shared ownership the Ownership and
the Memory Model course measured finds exactly its use here: the `Waker` passes into the
future's `poll` method as a reference from within `Context::from_waker`, but the notifier
itself also needs to go on living in the executor's own loop. `Arc` lets the notifier exist
inside the `Waker` and in the executor's own variable at the same time — whichever is dropped
first, the other can keep using its own copy. `EmptyNotifier` follows the same arrangement,
even though the data it holds is empty.

#### Same Task, Two Executors, Same Poll Count

The following program constructs both a task that pauses once and one that is ready
immediately, from the same definitions, and runs each of them through two separate executors.

```rust
// two-executors.rs — the same task type, two different executors, the same poll count.
mod advance;
use advance::{run, run_loop, PausesOnce, ImmediatelyReady};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    let (value_a1, poll_count_a1) = run(PausesOnce {
        paused: false,
        name: "pauses-run",
        channel: sender.clone(),
    });
    let (value_a2, poll_count_a2) = run_loop(PausesOnce {
        paused: false,
        name: "pauses-loop",
        channel: sender.clone(),
    });

    let (value_b1, poll_count_b1) = run(ImmediatelyReady {
        name: "ready-run",
        channel: sender.clone(),
    });
    let (value_b2, poll_count_b2) = run_loop(ImmediatelyReady {
        name: "ready-loop",
        channel: sender.clone(),
    });

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!(
        "pauses once:       run={} run_loop={} (value {} / {})",
        poll_count_a1, poll_count_a2, value_a1, value_a2
    );
    println!(
        "ready immediately: run={} run_loop={} (value {} / {})",
        poll_count_b1, poll_count_b2, value_b1, value_b2
    );
    println!(
        "same poll count: pauses={} ready={}",
        poll_count_a1 == poll_count_a2,
        poll_count_b1 == poll_count_b2
    );
    println!();
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
pauses once:       run=2 run_loop=2 (value pauses-run / pauses-loop)
ready immediately: run=1 run_loop=1 (value ready-run / ready-loop)
same poll count: pauses=true ready=true

records (4):
  pauses-loop
  pauses-run
  ready-loop
  ready-run
```

**AP4.** Four separate calls run two different task types through two different executors, and
the result stays the same: the task that pauses once finishes in two polls on both executors,
the task that is ready immediately finishes in one poll on both. This stayed identical across
five separate runs. `PausesOnce`'s body never changed — it does not even know which executor
is calling it, it does nothing but call the `Waker` inside its `Context`. What determines the
poll count is the task's own `poll` logic; the executor only decides **how often** to trigger
that logic.

#### How Many Tasks Run at the Same Time

Every call to `run` and `run_loop` takes a single root future and does not release the calling
thread until that future finishes. This is a simplified version of what a real executor
generally does: an executor usually holds a queue of more than one task at once, deciding which
one to poll by selecting from that queue. The four calls in this lesson — the `PausesOnce` and
`ImmediatelyReady` instances run through each of the two executors — run independently, one
after another; none of them is ever polled at the same time as another. Whether an executor
watches **more than one** task at once is a separate design question, and it falls outside this
lesson's measurement — what is fixed here is the plainest form of advancing a single task. The
two executor implementations producing the same number is already enough to show that, even in
this plain form, the executor's choice adds nothing to the body. The new question that arises
once an executor starts watching more than one task at once — which one gets polled first, how
long one keeps another waiting — does not change the single-task number this lesson measures;
it is a separate question, one concerning only the **order** between tasks, and it falls
outside this lesson.

#### An Executor Decides When to Poll, Not What Happens

The difference between `run` and `run_loop` never shows up in the **observable** part of the
run: the same values, the same poll counts, the same set of records. The difference lives only
in the — unprinted, and this course forbids printing it — time that passes between a `Pending`
return and the next `poll` call. `run` genuinely puts the calling thread to sleep for that
time; `run_loop` keeps that same thread in a busy loop. Both produce the correct result, but
one releases the processor while waiting, the other does not. This is the same question the
Threads topic measured — what a thread does while it waits — reappearing here at the executor
level; but this lesson does not measure it through **duration**, it measures it through
**whether the poll count changes**, and shows that it does not.

The Traits, Generics and Error Handling course measured how many times a body ran — every
time, only when needed, or never. This lesson asks a different question: **who** advances a
body that runs the same number of times. The executor is exactly the answer to that question —
a subject that has no say in how many times a body runs, only in when those runs happen.
`run` and `run_loop` play this subject role in two different ways, yet both trigger the same
number of times.

This choice between the two implementations is not trivial in a real program.
`run_loop` keeps the processor constantly busy with its busy loop; the moment a task returns
`Pending`, even if it is not ready, the loop keeps querying it again and again. `run`, by
contrast, releases the processor while it waits on the condition variable and only comes back
once `wake` is called. Neither changes the poll count measured in this lesson, because
`PausesOnce`'s decision to return `Pending` is already made by looking at the `paused` field —
it gives the same answer regardless of how often it is asked from outside. In a future genuinely
waiting on a resource (a reply from the network, data from a file), this difference becomes
even more pronounced, but it still has no effect at all on the number this lesson
measures — the next lesson models exactly such a resource.

#### The Same Executor, Two Different Future Types

Both `run` and `run_loop` were written generically with the `F: Future` bound; this is why the
same function can be called for both `PausesOnce` and `ImmediatelyReady`, without changing a
single line of the body. The Traits, Generics and Error Handling course measured that a generic
body is generated separately by the compiler for each concrete type; the same thing happens
here — `run::<PausesOnce>` and `run::<ImmediatelyReady>` are two separate bodies the compiler
produces. This lesson does not return to that axis, but it benefits from its result: the
executor's code is written in one place, and it does not need to be rewritten for every new
future type. All four calls in `two-executors.rs` call the same two functions (`run`,
`run_loop`); none of them duplicates the executor's body.

#### The Limiting Measurement

Changing the executor does not change the task's body. `PausesOnce`'s `poll` method does not
even look at which executor is calling it — it looks at the value of the `paused` field and
makes its own decision. The same task produces the same record in both implementations; the
difference lives only in the polling schedule, not in the produced value or the record. This
limit shows what an executor **cannot** change: what a task produces, in how many steps it
produces it, or which way it branches. An executor can only change the **timing** of the `poll`
call; beyond that it has no authority over a task at all.

This limit becomes more visible in situations where more than one task coexists on the same
executor: an executor can decide which task gets polled **first**, but it cannot interfere with
how many times `Pending` a task returns within its own `poll`. This distinction stays hidden
in this lesson's single-task examples, because there is no other task to determine the
order; but the rule already holds here too — `run` and `run_loop` follow very different
scheduling strategies, yet neither could change the decision that `PausesOnce` finishes in two
polls.

#### Summary

- Rust's standard library has no executor; the loop that calls `poll` is hand-written.
- An executor is the component that decides what to do on a `Pending` return — waiting,
  re-polling immediately, or following some other strategy is the executor's choice.
- The same task type run through two different executors produces the same poll count: the
  executor adds nothing to the task's body.
- The executor is the **subject** that advances a body; unlike the Traits, Generics and Error
  Handling course's "how many times did it run" question, this lesson asks "who advanced it."
- All four tasks run in this lesson fall into the **when-polled** class: none of them advances
  on its own, all four need an executor's `poll` call — only which executor makes that call
  changes, not the class itself.
- The real difference between the two executors lives not in the run count, but in the
  behavior during the — unprinted — time between a `Pending` return and the next poll;
  `run` and `run_loop` were written generically (`F: Future`), so the same executor body works
  with every future type.

#### Next Step

This lesson showed that an executor does not interfere with what a task **produces**, deciding
only **when** it is polled; two utterly different scheduling strategies produced an identical
poll count on the same task. Up to this point, every task was either ready immediately or
paused once and resumed on its own — in both cases, the reason for pausing was the task's own
logic, not dependent on an outside condition. The next lesson takes up the case where an
outside resource — input or output — is not yet ready: a resource that is not ready returns
`Pending`, and wakes the executor once it is ready. What is measured is still the poll count,
but this time that number changes depending on how long the resource takes to become ready,
and this lesson reuses one of the two executors built here — `run`.

### Asynchronous Input/Output

Source: https://academia.sh/en/courses/rust-concurrency/async-io

Suspension while waiting on input or output is modeled by a resource that returns Pending until it is ready, not by a real network or file; what is measured is how many polls it takes to complete, not the resource's time to become ready.

The previous lesson examined two kinds of task: one that never pauses and one that pauses once
by its own decision. In both, the reason for pausing was the task's own logic — it depended on
no outside condition. Most of what a program actually waits on in the real world is not like
this: a network connection takes an uncertain amount of time to respond, a read request sent to
a file system waits for hardware to become ready. This lesson takes up this kind of wait — but,
per the course's ban on environment-dependent data, without using a real network or file. The
wait is built with a model in which a resource that is not ready returns `Pending`, and wakes
itself once it is ready.

The reason for not using a real network socket or file descriptor is not just convenience. The
determinism constraint the course has kept since its start requires that a run produce the same
number no matter which machine it runs on. How long a real network request takes depends on the
network itself; how long a real file read takes depends on the storage device — this would make
it impossible for the course to produce an identical output across five separate runs. Instead,
the **abstract shape** of the real-world "resource is not ready yet" situation is captured: a
`poll` call returning `Pending` and later turning into a `Ready`. Why the resource is not
ready — a network delay, a disk access — falls outside this model; what stays inside the model
is only how many times it takes to be asked before it is ready.

#### Modeling a Resource That Is Not Ready

`MockResource` is the most reduced form of an input/output resource: it carries how many polls
until it becomes ready in a counter. On every `poll` call, if the counter is nonzero it
decreases by one, returns `Pending`, and wakes itself; once it reaches zero it writes a record
to the channel and returns `Ready`.

```rust
// advance.rs — a slice of the core: a future modeling a resource that is not ready.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

// models an input/output resource that is not ready: each poll decreases the remaining
// count by one; once remaining reaches zero it is considered ready and sends the value.
pub struct MockResource {
    pub remaining: u32,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for MockResource {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.remaining == 0 {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.remaining -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

pub async fn read_data(resource: MockResource, channel: Sender<String>) -> &'static str {
    let _ = resource.await;
    channel.send(String::from("read-complete")).unwrap();
    "read"
}
```

`MockResource` is the generalized form of the previous lesson's `PausesOnce`: `PausesOnce`
paused exactly once, `MockResource` pauses for however many times the `remaining` field says.
In a real program, this number would stand in for a hardware interrupt, an operating system
notification, or a network packet's arrival; here it is only an integer, and it determines the
resource's **behavior** — not the real event it stands in for. `read_data` is an `async fn`
awaiting this resource: it does not advance itself until `resource.await` completes, exactly
like the outer body awaiting an inner future in the previous lesson.

`MockResource`'s `poll` method calls `cx.waker().wake_by_ref()` on every `Pending` return —
just like `PausesOnce`. This is the most important difference from a real resource: a real
network socket does not wake itself; a notification mechanism belonging to the operating
system (a subsystem that tells the runtime when a socket is ready) calls the `Waker`.
`MockResource` imitates this outside notification — having no timer of its own, it chooses to
wake itself immediately every time and decrease `remaining` by one on the next `poll` call.
The result, seen from outside, is the same: the executor sees `Pending`, gets a chance to poll
again after some time, and the resource eventually becomes ready.

#### How Many Polls, How Much Waiting

The following program runs resources constructed with three different delays: one short (waits
one poll), one long (waits four polls), and one awaited from inside an `async fn`.

```rust
// input-output.rs — how many polls it takes a resource that is not ready to complete.
mod advance;
use advance::{run, read_data, MockResource};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    let (_value_a, poll_count_a) = run(MockResource {
        remaining: 1,
        name: "resource-short-wait",
        channel: sender.clone(),
    });
    let (_value_b, poll_count_b) = run(MockResource {
        remaining: 4,
        name: "resource-long-wait",
        channel: sender.clone(),
    });

    let resource_c = MockResource {
        remaining: 2,
        name: "resource-inner",
        channel: sender.clone(),
    };
    let (_value_c, poll_count_c) = run(read_data(resource_c, sender.clone()));

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!("short wait (remaining=1) -> completed in {} polls", poll_count_a);
    println!("long wait  (remaining=4) -> completed in {} polls", poll_count_b);
    println!("read, waiting on an inner resource -> completed in {} polls", poll_count_c);
    println!();
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
short wait (remaining=1) -> completed in 2 polls
long wait  (remaining=4) -> completed in 5 polls
read, waiting on an inner resource -> completed in 3 polls

records (4):
  read-complete
  resource-inner
  resource-long-wait
  resource-short-wait
```

**AP5.** The poll count is one more than the `remaining` field's starting value:
`remaining: 1` completes in two polls, `remaining: 4` in five. The rule belongs to the same
family as the $n+1$ rule in the `async`/`await` lesson — there it was $n$ consecutive
suspension points, here it is $n$ consecutive `Pending` returns; both count the final
call — the one returning `Ready` — into the total. `resource-inner` (`remaining: 2`) is
awaited from inside `read_data` and completes in three polls — two `Pending` plus one
`Ready`, exactly the count it would have completed in if handed directly to `run`. This stayed
identical across five separate runs.

#### The Waiting Task's Class

`resource-short-wait` and `resource-long-wait` fall into the **when-polled** class: both are
handed directly to `run`, both advance according to their own `remaining` counters.
`read_data`, by contrast, lands in the same position as the previous lesson's
`await_inner` — its own advancement depends on `resource_c` completing, and so it falls into
the **when-something-else-finishes** class. `resource_c` itself is still **when-polled** — the
two classes sit together here too, as in the previous lesson, in different layers of the same
task tree.

The Standard Library and Network Applications course measured what a value crossing a network
boundary keeps and loses of its **type information** — in what form the type on one side of
the boundary reaches the other side. This lesson measures the same kind of boundary from a
different axis: what a task **needs in order to advance** while it waits on an input/output
resource. Type information is never in question here — the value `MockResource` produces is
already `&'static str` from the start; the boundary separates not a type's shape, but a task's
progress condition.

Both measurements take up the same boundary — the boundary between a program's own code and a
resource coming from outside — but they ask different questions. There the question was "does
the value crossing this boundary keep the same type"; here the question is "what does this
side do until the resource on the other side of the boundary is ready." The difference between
`resource-short-wait` and `resource-long-wait` is not a type difference — both come from the
same `MockResource` type, both share the same `Output`; the difference is only in the starting
value of the `remaining` field, that is, in how often the progress condition gets satisfied.

#### The Executor Still Does Not Matter Here

The Executors lesson showed that two different executor implementations poll the same task the
same number of times. The same result holds for a modeled input/output resource too. The
following excerpt carries the previous lesson's `run_loop` over into this lesson and runs the
same `MockResource` through two different executors.

```rust
// advance.rs — the core slice changes: the previous lesson's second executor (run_loop)
// is introduced here, the same MockResource is compared across two executors.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

pub struct EmptyNotifier;

impl Wake for EmptyNotifier {
    fn wake(self: Arc<Self>) {}
}

pub fn run_loop<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(EmptyNotifier);
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        if let Poll::Ready(x) = task.as_mut().poll(&mut context) {
            return (x, poll_count);
        }
    }
}

pub struct MockResource {
    pub remaining: u32,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for MockResource {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.remaining == 0 {
            self.channel.send(self.name.to_string()).unwrap();
            Poll::Ready(self.name)
        } else {
            self.remaining -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}
```

```rust
// two-executors-io.rs — the same modeled resource, two different executors, the same poll count.
mod advance;
use advance::{run, run_loop, MockResource};
use std::sync::mpsc::channel;

fn main() {
    let (sender, receiver) = channel::<String>();

    let (_value_1, poll_count_1) = run(MockResource {
        remaining: 3,
        name: "resource-run",
        channel: sender.clone(),
    });
    let (_value_2, poll_count_2) = run_loop(MockResource {
        remaining: 3,
        name: "resource-loop",
        channel: sender.clone(),
    });

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!("run={} run_loop={} same: {}", poll_count_1, poll_count_2, poll_count_1 == poll_count_2);
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
run=4 run_loop=4 same: true
records (2):
  resource-loop
  resource-run
```

**AP6.** Two resources constructed with the same `remaining: 3` value, run through two utterly
different executors, produce an identical poll count (4). `MockResource`'s `poll` method has
no idea which executor is calling it — it looks only at its own `remaining` field. This carries
the previous lesson's finding over to a modeled input/output resource: the executor does not
interfere with **how long** a resource keeps someone waiting, it only interferes with what the
calling thread does while it waits — and that second difference lives in an area this course
forbids printing.

#### The Limiting Measurement

The modeled wait does not show a real delay's duration. The `remaining: 4` field requires five
polls, but there is no actual waiting at all between these five calls — `run`'s condition
variable wakes up immediately on every `wake_by_ref` call, because `MockResource` wakes itself
on every `Pending` return. A real resource (a network socket, a file descriptor) would make
this wake call at the moment it actually becomes ready, through an operating system
notification; here that notification is imitated by the `remaining` counter decreasing.
What is always measured is the **poll count** — not the waiting time, because that time
depends on the environment, and this course has forbidden printing it from the start.

One consequence of this limit: if the number written into the `remaining` field is made
larger, the poll count grows proportionally, but the run's actual duration does not change in
any measurable way — there is no real wait anywhere, only how many times the loop runs
changes. In a real program this relationship reverses: the poll count can stay fixed (an
executor can be designed to re-poll a resource only once it is genuinely ready), but the real
waiting time changes depending on how fast the resource itself is. Seeing that these two
axes — poll count and waiting time — can be independent of each other is necessary for
understanding what the model represents and what it does not: this lesson measures only the
first of the two, the one number this course has allowed to be printed from the very start.

#### Summary

- Suspension while waiting on input or output is built with a future modeling a resource that
  is not ready: if the resource is not ready it returns `Pending` and wakes itself.
- How many polls a resource takes to complete comes from the resource's own `remaining` logic
  and is deterministic — it stays identical across five separate runs.
- A directly polled resource falls into the **when-polled** class; an `async fn` awaiting it
  falls, for its own completion, into the **when-something-else-finishes** class.
- The Standard Library and Network Applications course measured a boundary through type
  information; this lesson measures the same boundary through a task's progress condition.
- The modeled wait does not show the real delay; what is measured is the poll count, not the
  time elapsed.
- The same modeled resource run through two different executors also produces the same poll
  count — the Executors lesson's finding holds unchanged for modeled input/output too.

#### Next Step

This topic built, in four steps, what a task needs in order to advance: a task does not
advance by being constructed, it advances only when polled; `async`/`await` makes visible in
the source where these polls pause and from where they resume; an executor decides **when**
these polls happen but does not interfere with the value a task produces; and an
input/output resource can extend this polling chain by a number tied to its readiness delay.
Across four lessons, the one number measured — how many times `poll` was called — stayed, every
time, derivable in advance from the source.

The remaining question is what happens to a task that is written in the source but never
completes, or is left uncompleted. This course's closing lesson takes up the cancellation
established by **dropping** a task: a dropped task is never polled again, and stays written in
the source like this lesson's `MockResource`, but shares the same fate as the `_never_polled`
in the Futures lesson — never advancing. A timeout is modeled from the same frame too, not
through duration but through poll count.

### Cancellation and Timeouts

Source: https://academia.sh/en/courses/rust-concurrency/cancellation-and-timeouts

The course's closing lesson measures the cancellation established by dropping a task: a dropped task is never polled again and falls into the not-at-all class; a timeout, too, is modeled not by a duration but by an exhausted poll budget.

The Asynchronous Input/Output lesson left open the question of a task that is written in the
source but never completes: a task can pause forever, so what happens if it is never touched
again? This course's closing lesson takes up that question. In Rust's asynchronous model,
there is no separate call to stop a task — a future only advances for as long as it is polled,
so never polling it again is itself what cancels that task.

Go's Concurrency course measured cancellation as one of four ways a goroutine can stop — a
stopping mechanism. This lesson measures the same concept differently: a canceled task has no
execution flow of its own to stop — cancellation here is a state of **not advancing**, the fate
of a task that sits written in the source and is never polled again. Two separate paths are
measured: a task can be canceled by being manually dropped, or it can time out by being
dropped automatically once a poll budget runs out. Both fall into the same class.

#### A Dropped Task Is Never Polled Again

Being dropped does not by itself mean being canceled. A completed task, too, once the value it
produced has been read, does not go on sitting in memory — it too gets dropped, but that is a
cleanup, not a cancellation. The distinction is in whether the task **has finished** at the
moment it is dropped: a task dropped before finishing will never be polled again, a finished
task has nothing left to poll anyway. The following core excerpt holds this distinction with
two fields: `started` marks whether the task was polled at least once, `completed` marks
whether it produced its final value. A task dropped while only the first is true and the
second is false is counted as canceled; this is measured by sending a record from the task's
own `Drop` implementation.

```rust
// advance.rs — a slice of the core: cancellation established by dropping, and a timeout
// modeled with a poll budget.

use std::future::Future;
use std::pin::Pin;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};

pub struct Notifier {
    pub ready: Mutex<bool>,
    pub condvar: Condvar,
}

impl Wake for Notifier {
    fn wake(self: Arc<Self>) {
        *self.ready.lock().unwrap() = true;
        self.condvar.notify_one();
    }
}

pub fn run<F: Future>(task: F) -> (F::Output, u32) {
    let mut task = Box::pin(task);
    let n = Arc::new(Notifier {
        ready: Mutex::new(false),
        condvar: Condvar::new(),
    });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let mut poll_count = 0;
    loop {
        poll_count += 1;
        match task.as_mut().poll(&mut context) {
            Poll::Ready(x) => return (x, poll_count),
            Poll::Pending => {
                let mut ready = n.ready.lock().unwrap();
                while !*ready {
                    ready = n.condvar.wait(ready).unwrap();
                }
                *ready = false;
            }
        }
    }
}

// a task that can be manually dropped after being polled once: started work is
// measurable, completion is only recorded if a second poll actually happens.
pub struct CancelableJob {
    pub name: &'static str,
    pub started: bool,
    pub completed: bool,
    pub channel: Sender<String>,
}

impl Future for CancelableJob {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if !self.started {
            self.started = true;
            self.channel.send(format!("{}-started", self.name)).unwrap();
            cx.waker().wake_by_ref();
            Poll::Pending
        } else {
            self.completed = true;
            self.channel.send(format!("{}-completed", self.name)).unwrap();
            Poll::Ready(self.name)
        }
    }
}

impl Drop for CancelableJob {
    fn drop(&mut self) {
        if self.started && !self.completed {
            self.channel.send(format!("{}-dropped", self.name)).unwrap();
        }
    }
}

// a long job that advances with a counter decreasing by one on every poll; if it is
// dropped before completing, the remaining amount stays above zero.
pub struct LongJob {
    pub remaining: u32,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl Future for LongJob {
    type Output = &'static str;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
        if self.remaining == 0 {
            self.channel.send(format!("{}-completed", self.name)).unwrap();
            Poll::Ready(self.name)
        } else {
            self.remaining -= 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

impl Drop for LongJob {
    fn drop(&mut self) {
        if self.remaining > 0 {
            self.channel.send(format!("{}-dropped-midway", self.name)).unwrap();
        }
    }
}

// a task wrapping an inner task with a limited number of polling rights: once the
// budget runs out the inner task is dropped immediately and never polled again.
pub struct TimedTask<F: Future> {
    pub inner: Option<Pin<Box<F>>>,
    pub budget: u32,
    pub name: &'static str,
    pub channel: Sender<String>,
}

impl<F: Future> Future for TimedTask<F> {
    type Output = Result<F::Output, ()>;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<F::Output, ()>> {
        if self.budget == 0 {
            self.inner = None;
            self.channel.send(format!("{}-timed-out", self.name)).unwrap();
            return Poll::Ready(Err(()));
        }
        self.budget -= 1;
        match self.inner.as_mut().unwrap().as_mut().poll(cx) {
            Poll::Ready(x) => Poll::Ready(Ok(x)),
            Poll::Pending => Poll::Pending,
        }
    }
}
```

```rust
// cancellation.rs — cancellation established by dropping and a timeout modeled with a
// poll budget, in a single run.
mod advance;
use advance::{run, CancelableJob, LongJob, TimedTask, Notifier};
use std::future::Future;
use std::sync::mpsc::channel;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Waker};

fn main() {
    let (sender, receiver) = channel::<String>();

    // --- completed task: run polls it until it finishes ---
    let (result, poll_count) = run(CancelableJob {
        name: "task-completed",
        started: false,
        completed: false,
        channel: sender.clone(),
    });

    // --- canceled task: polled once by hand, then dropped ---
    let mut canceled = Box::pin(CancelableJob {
        name: "task-canceled",
        started: false,
        completed: false,
        channel: sender.clone(),
    });
    let n = Arc::new(Notifier { ready: Mutex::new(false), condvar: Condvar::new() });
    let waker: Waker = n.clone().into();
    let mut context = Context::from_waker(&waker);
    let _ = canceled.as_mut().poll(&mut context);
    drop(canceled);

    // --- timeout with a poll budget: one finishes within budget, the other exceeds it ---
    let (result_a, budget_poll_count_a) = run(TimedTask {
        inner: Some(Box::pin(LongJob { remaining: 1, name: "job-a", channel: sender.clone() })),
        budget: 3,
        name: "job-a",
        channel: sender.clone(),
    });
    let (result_b, budget_poll_count_b) = run(TimedTask {
        inner: Some(Box::pin(LongJob { remaining: 5, name: "job-b", channel: sender.clone() })),
        budget: 2,
        name: "job-b",
        channel: sender.clone(),
    });

    drop(sender);
    let mut records: Vec<String> = receiver.try_iter().collect();
    records.sort();

    println!("task-completed finished in {} polls, result: {}", poll_count, result);
    println!("task-canceled manually dropped after 1 poll");
    println!();
    println!("job-a (budget=3, remaining=1) -> {} polls, result: {:?}", budget_poll_count_a, result_a);
    println!("job-b (budget=2, remaining=5) -> {} polls, result: {:?}", budget_poll_count_b, result_b);
    println!();
    println!("records ({}):", records.len());
    for record in &records {
        println!("  {}", record);
    }
}
```

```
task-completed finished in 2 polls, result: task-completed
task-canceled manually dropped after 1 poll

job-a (budget=3, remaining=1) -> 2 polls, result: Ok("job-a")
job-b (budget=2, remaining=5) -> 3 polls, result: Err(())

records (7):
  job-a-completed
  job-b-dropped-midway
  job-b-timed-out
  task-canceled-dropped
  task-canceled-started
  task-completed-completed
  task-completed-started
```

**AP7.** `task-completed` finishes in two polls — one pause, one completion — carrying forward
the familiar $n+1$ rule from the `async`/`await` lesson. `task-canceled` is only polled once by
hand, sends its `-started` record, and is then dropped directly; this drop triggers the `Drop`
implementation and sends the `-dropped` record. These two records make the task's **started
work** measurable: one poll happened, one cleanup ran. But `task-canceled-completed` is
nowhere among the seven records — because the second poll never happened. The canceled task
falls into the **not-at-all** class: written in the source, touched once, but dropped before
completing.

#### Timeout When the Poll Budget Runs Out

In this course, a timeout is built not with a duration but with a number: the excerpt above's
`TimedTask` allows the task it wraps to be polled at most `budget` times. If the wrapped task
finishes on its own before the budget runs out, the result comes back as an `Ok` value; once
the budget runs out, `TimedTask` immediately drops the task inside it — assigning `None` to
`self.inner` — and never touches it again. This is the automated form of the manual drop
mechanism from the previous section: the decision is made not by the task itself, but by the
budget wrapping it.

**AP8.** `job-a` was constructed with a budget of three polls and used only two of them,
finishing with `Ok("job-a")`. `job-b` was constructed with a budget of only two polls but
needed five; on the third call the budget dropped to zero, so the wrapper drops the inner task
and returns `Err(())`. The inner task's own `Drop` implementation sends the
`-dropped-midway` record, because at the moment it was dropped it still had three units of
work left — of five units of work, two had completed, three were **started but not
finished**. The `job-b-completed` record, too, is nowhere among the seven records. `job-a`
falls into the **when-polled** class; `job-b` falls into the **not-at-all** class once its
budget runs out — the same fate as a manually dropped task, triggered by a different cause.

#### The Limiting Measurement

Dropping does not reveal a task's **internal state**. The `Drop` implementation only records
what was hand-coded for it to write — `CancelableJob` tracks the `started` and `completed`
flags, `LongJob` tracks the remaining counter value. This means the language does not expose
any general "how much work got done" information on its own: unless the author of a task type
makes this information measurable, what state a dropped value was in is unknowable outside the
source code.

The poll budget is not a duration either. The `budget` field is an upper bound on how many
times something gets polled, but it has no relationship at all to how long a task actually
takes in the real world — the same budget runs out after the same number of polls whether every
poll of a task is heavy or every poll is instant. A real timeout mechanism pairs this number
with a clock; because this course uses no clock, a timeout here is only a measure of **how many
times it was tried**, not of how long it waited.

#### Closing Table

| Lesson | Measured unit | Progress condition | Limiting measurement |
|---|---|---|---|
| Thread Creation | Thread spawning and joining | on its own · when something else finishes | The order of records is not measured, only whether the expected count arrived |
| Message Passing with Channels | Ownership and receipt of a message passed through a channel | on its own · when something else finishes | The waiting side waits indefinitely; a missing record is measured by the gap between expected and received |
| Thread Safety in the Type System | An `Arc<Mutex<...>>` value able to cross the boundary | on its own · when something else finishes | The type's permission prevents data races; it does not guarantee order-dependent correctness |
| Mutex and Read-Write Lock | The side waiting on a lock | on its own · when something else finishes | Which of two waiters enters first is not measured; the lock only protects the data |
| Atomic Operations and Memory Ordering | A lock-free atomic counter | on its own | Being atomic is not being ordered; which increment happens first is not measured |
| Futures | A polled task and a never-polled task | when polled · not at all | An unpolled task is not an error; it leaves no trace, silently |
| `async`/`await` | The suspension point and the poll count | when polled · when something else finishes | The number of suspensions is measured, not how long each one lasts |
| Executors | The same task, two executor implementations | when polled | The executor decides when to poll; it does not touch the task's body |
| Asynchronous Input/Output | A modeled input/output resource | when polled · when something else finishes | The modeled wait does not represent a real delay; what is measured is the poll count |
| Cancellation and Timeouts | A dropped task and a timed-out task | not at all | Dropping does not reveal internal state; the budget is an arbitrary poll limit |

#### Summary

- Dropping a task is the cancellation call in Rust's asynchronous model; a dropped task is
  never polled again and falls into the **not-at-all** class.
- A drop is a cancellation only if it happens before the task completes; dropping a completed
  task is ordinary cleanup — the two states were kept apart by separate records in this lesson.
- A timeout was modeled not with a duration but with a poll budget: a task whose budget ran
  out was dropped immediately and never polled again — it, too, fell into the **not-at-all**
  class.
- A canceled task's started work stayed measurable — records existed showing that it had
  started and had been dropped — but a record showing that it had completed never arrived.
- Go's Concurrency course measured cancellation as a stopping mechanism; this lesson measured
  the same concept as a state of not advancing.
- The closing table brought the course's ten lessons and its four classes (on its own, when
  polled, when something else finishes, not at all) together in a single view.

#### Course Wrap-Up

This course carried a single question through ten lessons: what does a unit need in order to
advance. The answer was always one of the same four names, and the closing table showed how
these four names were distributed across ten different units — threads advanced on their own
or by waiting on each other, locks and channels carried that waiting, futures advanced only
when polled, and this closing lesson showed that a task can also never advance at
all — sitting written in the source and never touched again. This was the repository's fifth,
and for now last, concurrency course; five courses together toured the same territory with five
different questions.

The next course, The Rust Ecosystem and Advanced Topics, looks at the rest of the language
beyond concurrency: macros, the sections of code voluntarily stepped outside the compiler's
safety guarantees, call interfaces built with other languages, and a Rust project's life cycle
from dependency management to publication. The progress question measured in this
lesson — what a unit needs in order to advance — is not asked again there; the next course will
build its own measurement axis from its own subject.

