Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rbe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push, pull_request]
env:
# Update the language picker in index.hbs to link new languages.
LANGUAGES: ja zh es ko
MDBOOK_VERSION: 0.5.1
MDBOOK_VERSION: 0.5.4

jobs:
test:
Expand Down
2 changes: 1 addition & 1 deletion book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ additional-css = [
use-boolean-and = true

[rust]
edition = "2021"
edition = "2024"

[build]
extra-watch-dirs = ["po"]
Expand Down
25 changes: 25 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
- [Conventions](cargo/conventions.md)
- [Tests](cargo/test.md)
- [Build Scripts](cargo/build_scripts.md)
- [Workspaces, features, and profiles](cargo/workspaces.md)
- [Tooling: fmt, clippy, doc, audit](cargo/tooling.md)

- [Attributes](attribute.md)
- [`dead_code`](attribute/unused.md)
Expand All @@ -111,8 +113,10 @@
- [Associated items](generics/assoc_items.md)
- [The Problem](generics/assoc_items/the_problem.md)
- [Associated types](generics/assoc_items/types.md)
- [Generic Associated Types](generics/gats.md)
- [Phantom type parameters](generics/phantom.md)
- [Testcase: unit clarification](generics/phantom/testcase_units.md)
- [Const generics](generics/const_generics.md)

- [Scoping rules](scope.md)
- [RAII](scope/raii.md)
Expand Down Expand Up @@ -141,6 +145,7 @@
- [Drop](trait/drop.md)
- [Iterators](trait/iter.md)
- [`impl Trait`](trait/impl_trait.md)
- [`async` in traits](trait/async_traits.md)
- [Clone](trait/clone.md)
- [Supertraits](trait/supertraits.md)
- [Disambiguating overlapping traits](trait/disambiguating.md)
Expand All @@ -153,6 +158,7 @@
- [DRY (Don't Repeat Yourself)](macros/dry.md)
- [DSL (Domain Specific Languages)](macros/dsl.md)
- [Variadics](macros/variadics.md)
- [Procedural macros](macros/proc.md)

- [Error handling](error.md)
- [`panic`](error/panic.md)
Expand All @@ -174,6 +180,7 @@
- [Other uses of `?`](error/multiple_error_types/reenter_question_mark.md)
- [Wrapping errors](error/multiple_error_types/wrap_error.md)
- [Iterating over `Result`s](error/iter_result.md)
- [Error reporting with `anyhow` and `thiserror`](error/reporting.md)

- [Std library types](std.md)
- [Box, stack and heap](std/box.md)
Expand All @@ -188,10 +195,17 @@
- [HashSet](std/hash/hashset.md)
- [`Rc`](std/rc.md)
- [`Arc`](std/arc.md)
- [`Cell` and `RefCell`](std/cell.md)
- [`OnceLock` and `LazyLock`](std/once.md)
- [`Cow`](std/cow.md)
- [More collections](std/collections.md)
- [Iterators in depth](std/iter.md)
- [Capstone: word frequency](std/word_freq.md)

- [Std misc](std_misc.md)
- [Threads](std_misc/threads.md)
- [Testcase: map-reduce](std_misc/threads/testcase_mapreduce.md)
- [Shared state: `Mutex`, `RwLock`, atomics](std_misc/sync.md)
- [Channels](std_misc/channels.md)
- [Path](std_misc/path.md)
- [File I/O](std_misc/file.md)
Expand All @@ -206,18 +220,29 @@
- [Argument parsing](std_misc/arg/matching.md)
- [Foreign Function Interface](std_misc/ffi.md)

- [Async](async.md)
- [`async` and `.await`](async/await_syntax.md)
- [Spawning tasks and channels](async/spawn.md)
- [Streams of messages](async/streams.md)
- [Capstone: fan-out chat](async/echo.md)

- [Testing](testing.md)
- [Unit testing](testing/unit_testing.md)
- [Documentation testing](testing/doc_testing.md)
- [Integration testing](testing/integration_testing.md)
- [Dev-dependencies](testing/dev_dependencies.md)
- [Property testing](testing/property.md)

- [Unsafe Operations](unsafe.md)
- [Inline assembly](unsafe/asm.md)
- [`MaybeUninit`](unsafe/maybe_uninit.md)

- [Compatibility](compatibility.md)
- [Raw identifiers](compatibility/raw_identifiers.md)

- [Ecosystem](ecosystem.md)
- [CLI + JSON walkthrough](ecosystem/cli_json.md)

- [Meta](meta.md)
- [Documentation](meta/doc.md)
- [Playground](meta/playground.md)
31 changes: 31 additions & 0 deletions src/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Async

Synchronous code blocks the thread while waiting: a network read parks
the whole thread until bytes arrive. Asynchronous code instead expresses
waiting as a *future* — a value that is lazy (it does nothing until
polled) and that a *runtime* drives to completion, freeing the thread
to run other futures in the meantime.

Rust's standard library provides the core pieces (`Future`, `async`,
`.await`), but no runtime and no timers or network drivers. This
chapter uses [`tokio` 1.x][tokio] as its runtime, the most widely used
choice. Every snippet that needs tokio is marked `ignore` (it is not
compiled by `mdbook test`) and carries its dependency as a comment
header:

```rust,ignore
// Cargo.toml: tokio = { version = "1", features = ["full"] }

#[tokio::main]
async fn main() {
println!("hello from async main");
}
```

The three leaves cover [`await` syntax](async/await_syntax.md),
[spawning and channels](async/spawn.md), and [message
streams](async/streams.md). To run any of them locally, create a binary
crate, add the `Cargo.toml` line from its header, and paste the body
into `src/main.rs`.

[tokio]: https://docs.rs/tokio/latest/tokio/
102 changes: 102 additions & 0 deletions src/async/await_syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# `async` and `.await`

An `async fn` does not run its body when called: it returns a future,
and the body runs only once the future is `.await`ed. `.await` pauses
the current future (yielding the thread back to the runtime) until the
awaited future completes, then resumes with its value. `async` blocks
work the same way for inline futures.

```rust,editable,ignore,mdbook-runnable
// Cargo.toml: tokio = { version = "1", features = ["full"] }

async fn greet(name: &str) -> String {
// Calling `greet` only builds the future; `.await` runs it.
format!("hello {}", name)
}

#[tokio::main]
async fn main() {
let message = greet("ferris").await;
println!("{}", message);

let ready = async {
40 + 2
};
println!("inline: {}", ready.await);
}
```

Values held across `.await` must survive a suspension, so the compiler
requires them to be `Send` when the runtime may resume the future on
another thread. The classic footgun is holding a `MutexGuard` across
an await: the lock stays held while the task is parked, blocking every
other task that wants it. Drop the guard (or copy out what you need)
before awaiting.

```rust,editable,ignore,mdbook-runnable
// Cargo.toml: tokio = { version = "1", features = ["full"] }

use std::sync::Mutex;

async fn fetch_cached(cache: &Mutex<String>) -> String {
// Copy the data out so the guard drops before any `.await`.
let cached = cache.lock().unwrap().clone();
if !cached.is_empty() {
return cached;
}

let fresh = slow_network_call().await;
*cache.lock().unwrap() = fresh.clone();
fresh
}

async fn slow_network_call() -> String {
"fresh-data".to_string()
}

#[tokio::main]
async fn main() {
let cache = Mutex::new(String::new());
println!("first: {}", fetch_cached(&cache).await);
println!("cached: {}", fetch_cached(&cache).await);
}
```

### See also:

[`std::future`][future], [`tokio::task`][task], and [Spawning](spawn.md).

[future]: https://doc.rust-lang.org/std/future/index.html
[task]: https://docs.rs/tokio/latest/tokio/task/index.html

### Exercise: Add a second awaited call

Task: Extend the program with a second `async fn` and await both results in order.

<details><summary>Hint</summary>

Each call builds an independent future, so awaiting them one after another runs each body in turn.

</details>

<details><summary>Solution</summary>

```rust,editable,ignore,mdbook-runnable
// Cargo.toml: tokio = { version = "1", features = ["full"] }

async fn greet(name: &str) -> String {
format!("hello {}", name)
}

async fn farewell(name: &str) -> String {
format!("goodbye {}", name)
}

#[tokio::main]
async fn main() {
let hello = greet("ferris").await;
let bye = farewell("ferris").await;
println!("{} and {}", hello, bye);
}
```
</details>
93 changes: 93 additions & 0 deletions src/async/echo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Capstone: fan-out chat

This page combines everything from the Async chapter: two client tasks
send messages through one `mpsc` channel while the main task receives
until every sender is gone. No `join!` tracks the clients — dropping
the last `Sender` (including the original) closes the channel, and the
`None` from `recv()` is the shutdown signal.

```rust,editable,ignore,mdbook-runnable
// Cargo.toml: tokio = { version = "1", features = ["full"] }

use tokio::sync::mpsc;

async fn client(id: u32, tx: mpsc::Sender<String>) {
for round in 1..=3 {
tx.send(format!("client {id} round {round}")).await.unwrap();
}
// `tx` drops here; when the last sender drops, the channel closes.
}

#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(16);

tokio::spawn(client(1, tx.clone()));
tokio::spawn(client(2, tx.clone()));
// The original sender is not needed anymore: the channel now closes
// exactly when both clients finish.
drop(tx);

let mut received = 0;
while let Some(msg) = rx.recv().await {
println!("got: {msg}");
received += 1;
}

assert_eq!(received, 6);
println!("all clients done");
}
```

The pattern generalizes: senders are producers, the receiver loop is
the consumer, and closing is structural rather than messaged. Adding a
producer never touches the shutdown logic.

### See also:

[Spawning tasks and channels](spawn.md) and [Streams of
messages](streams.md).

### Exercise: Add a third client

Task: Add a third client task and update the assertion to match.

<details><summary>Hint</summary>

Every client sends the same number of messages, so the total scales directly with the client count.

</details>

<details><summary>Solution</summary>

```rust,editable,ignore,mdbook-runnable
// Cargo.toml: tokio = { version = "1", features = ["full"] }

use tokio::sync::mpsc;

async fn client(id: u32, tx: mpsc::Sender<String>) {
for round in 1..=3 {
tx.send(format!("client {id} round {round}")).await.unwrap();
}
}

#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(16);

tokio::spawn(client(1, tx.clone()));
tokio::spawn(client(2, tx.clone()));
tokio::spawn(client(3, tx.clone()));
drop(tx);

let mut received = 0;
while let Some(msg) = rx.recv().await {
println!("got: {msg}");
received += 1;
}

assert_eq!(received, 9);
println!("all clients done");
}
```
</details>
Loading
Loading