diff --git a/.github/workflows/rbe.yml b/.github/workflows/rbe.yml index 5a5ac4bd5d..50dfb2e2e3 100644 --- a/.github/workflows/rbe.yml +++ b/.github/workflows/rbe.yml @@ -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: diff --git a/book.toml b/book.toml index 68d18deb05..f4caa7eefd 100644 --- a/book.toml +++ b/book.toml @@ -22,7 +22,7 @@ additional-css = [ use-boolean-and = true [rust] -edition = "2021" +edition = "2024" [build] extra-watch-dirs = ["po"] diff --git a/src/SUMMARY.md b/src/SUMMARY.md index b8e6ada917..e381df7f3c 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/src/async.md b/src/async.md new file mode 100644 index 0000000000..80c2d24acb --- /dev/null +++ b/src/async.md @@ -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/ diff --git a/src/async/await_syntax.md b/src/async/await_syntax.md new file mode 100644 index 0000000000..94f7d2bef8 --- /dev/null +++ b/src/async/await_syntax.md @@ -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 { + // 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. + +
Hint + +Each call builds an independent future, so awaiting them one after another runs each body in turn. + +
+ +
Solution + +```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); +} +``` +
diff --git a/src/async/echo.md b/src/async/echo.md new file mode 100644 index 0000000000..ad176bd5a4 --- /dev/null +++ b/src/async/echo.md @@ -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) { + 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. + +
Hint + +Every client sends the same number of messages, so the total scales directly with the client count. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +use tokio::sync::mpsc; + +async fn client(id: u32, tx: mpsc::Sender) { + 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"); +} +``` +
diff --git a/src/async/spawn.md b/src/async/spawn.md new file mode 100644 index 0000000000..d5f8d271bb --- /dev/null +++ b/src/async/spawn.md @@ -0,0 +1,91 @@ +# Spawning tasks and channels + +`#[tokio::main]` turns `main` into an async entry point by building a +runtime and blocking on the future. Inside it, `tokio::spawn` runs a +future as a background task and returns a `JoinHandle`; awaiting the +handle yields the task's return value. `tokio::join!` waits for several +futures at once without spawning. + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +async fn work(id: u32) -> u32 { + id * 10 +} + +#[tokio::main] +async fn main() { + // Run in the background; `handle` is a future for its result. + let handle = tokio::spawn(work(1)); + // Run two futures concurrently on this task and wait for both. + let (a, b) = tokio::join!(work(2), work(3)); + + println!("spawned: {}", handle.await.unwrap()); + println!("joined: {} {}", a, b); +} +``` + +Tasks communicate through channels. An `mpsc` (multi-producer, +single-consumer) channel clones its sender across tasks while one +receiver collects; `send().await` applies backpressure when the buffer +is full. + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +use tokio::sync::mpsc; + +#[tokio::main] +async fn main() { + let (tx, mut rx) = mpsc::channel(8); + + tokio::spawn(async move { + tx.send("hello").await.unwrap(); + tx.send("world").await.unwrap(); + // `tx` drops here, closing the channel. + }); + + while let Some(msg) = rx.recv().await { + println!("got: {}", msg); + } +} +``` + +### See also: + +[`tokio::spawn`][spawn], [`tokio::join!`][join], and +[`tokio::sync::mpsc`][mpsc]. + +[spawn]: https://docs.rs/tokio/latest/tokio/task/fn.spawn.html +[join]: https://docs.rs/tokio/latest/tokio/macro.join.html +[mpsc]: https://docs.rs/tokio/latest/tokio/sync/mpsc/index.html + +### Exercise: Spawn two tasks and join both + +Task: Spawn a second task beside the first and wait for both with `join!`. + +
Hint + +Each spawned task hands back a handle that can be awaited like any other future. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +async fn work(id: u32) -> u32 { + id * 10 +} + +#[tokio::main] +async fn main() { + let first = tokio::spawn(work(1)); + let second = tokio::spawn(work(2)); + + let (a, b) = tokio::join!(first, second); + println!("results: {} {}", a.unwrap(), b.unwrap()); +} +``` +
diff --git a/src/async/streams.md b/src/async/streams.md new file mode 100644 index 0000000000..562ab3b016 --- /dev/null +++ b/src/async/streams.md @@ -0,0 +1,88 @@ +# Streams of messages + +A channel receiver used in a loop is the simplest async stream: each +`recv().await` yields the next message, and `None` ends the loop when +all senders drop. No extra crate is needed beyond tokio — think of it +as the async counterpart of iterating, where waiting replaces blocking. + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +use tokio::sync::mpsc; + +#[tokio::main] +async fn main() { + let (tx, mut rx) = mpsc::channel(8); + + tokio::spawn(async move { + for n in 1..=5 { + tx.send(n * n).await.unwrap(); + } + // All `tx` clones drop here, so the stream ends. + }); + + // Each iteration waits for the next message without blocking a thread. + let mut total = 0; + while let Some(n) = rx.recv().await { + total += n; + } + println!("sum of squares: {}", total); +} +``` + +Shutting the stream down is structural: dropping the last sender closes +it, and the receiver loop exits by itself. For richer combinators +(`map`, `filter`, `merge`), the [`tokio-stream`][tokio_stream] crate +wraps receivers in a `StreamExt` API that mirrors synchronous +iterators. + +### See also: + +[`tokio::sync::mpsc`][mpsc], [`tokio-stream`][tokio_stream], and +[Spawning](spawn.md). + +[mpsc]: https://docs.rs/tokio/latest/tokio/sync/mpsc/index.html +[tokio_stream]: https://docs.rs/tokio-stream/latest/tokio_stream/ + +### Exercise: Sum a stream with a cutoff + +Task: Stop consuming the stream once the running total exceeds ten and report the count. + +
Hint + +The receive loop is ordinary control flow, so a threshold check inside it can break out early. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +use tokio::sync::mpsc; + +#[tokio::main] +async fn main() { + let (tx, mut rx) = mpsc::channel(8); + + tokio::spawn(async move { + for n in 1..=10 { + if tx.send(n).await.is_err() { + break; + } + } + }); + + let mut total = 0; + let mut count = 0; + while let Some(n) = rx.recv().await { + total += n; + count += 1; + if total > 10 { + break; + } + } + println!("stopped after {} values, total {}", count, total); +} +``` +
diff --git a/src/cargo/tooling.md b/src/cargo/tooling.md new file mode 100644 index 0000000000..c8bab7b34b --- /dev/null +++ b/src/cargo/tooling.md @@ -0,0 +1,68 @@ +# Tooling: fmt, clippy, doc, audit + +Three commands keep a Rust project healthy, and all of them run in CI. +`cargo fmt` normalizes layout, `cargo clippy` lints for common +mistakes, and `cargo doc` builds API documentation from doc comments. + +```shell +$ cargo fmt --all -- --check # fail CI if anything is unformatted +$ cargo clippy --all-targets --all-features -- -D warnings +$ cargo doc --no-deps --open # docs for your crates only, then open +``` + +Fix formatting with bare `cargo fmt`, and read a lint's full +explanation before silencing it — most clippy warnings are genuine +bugs (`needless_range_loop`, `unwrap_used` in libraries) rather than +style. Deny-by-default in CI, allow-by-exception in code with a +commented reason: + +```rust,ignore +#[allow(clippy::too_many_arguments)] // builder pattern defeats the lint's purpose here +fn connect(host: &str, port: u16, user: &str, password: &str, + database: &str, timeout_secs: u64, retries: u32) -> Connection { + // ... + # todo!() +} +``` + +For the supply chain, audit dependencies regularly: + +```shell +$ cargo install cargo-audit +$ cargo audit # known CVEs in your lockfile +$ cargo update -p some-crate # bump one dependency deliberately +``` + +### See also: + +[Tests](test.md), [Conventions](conventions.md), and [the Clippy +lint list][clippy]. + +[clippy]: https://rust-lang.github.io/rust-clippy/master/ + +### Exercise: Silence one lint properly + +Task: Fix a `clippy::needless_range_loop` warning the idiomatic way instead of allowing it. + +
Hint + +Iterating elements directly removes the indexing the lint complains about. + +
+ +
Solution + +```rust,editable +fn main() { + let values = vec![1, 2, 3, 4]; + + // Before: `for i in 0..values.len() { total += values[i]; }` + // lints `needless_range_loop`. Iterate the elements instead: + let mut total = 0; + for v in &values { + total += v; + } + println!("total: {}", total); +} +``` +
diff --git a/src/cargo/workspaces.md b/src/cargo/workspaces.md new file mode 100644 index 0000000000..8f9f13b6b2 --- /dev/null +++ b/src/cargo/workspaces.md @@ -0,0 +1,97 @@ +# Workspaces, features, and profiles + +Real projects outgrow a single crate. A *workspace* groups related +crates under one `Cargo.toml` so they share a lockfile and build +together, while *features* make optional functionality opt-in and +*profiles* tune compiler settings per build mode. + +```toml +# Cargo.toml at the workspace root. This package is virtual: it only +# organizes members, it produces no binary or library itself. +[workspace] +members = ["cli", "core"] +resolver = "2" + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +``` + +```toml +# cli/Cargo.toml: features let users pay only for what they enable. +[package] +name = "cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +core = { path = "../core" } +serde_json = "1" +# Optional: only built when the `tls` feature is requested. +tokio-tls = { version = "0.1", optional = true } + +[features] +default = [] +tls = ["dep:tokio-tls"] +``` + +```shell +$ cargo build -p cli +$ cargo run -p cli --features tls -- --name ferris +$ cargo test --workspace +``` + +```toml +# Release tuning lives in profiles, not in code. +[profile.release] +opt-level = 3 +lto = true +``` + +`Cargo.lock` pins the exact versions used; commit it for binaries (so +every checkout builds identically) and leave it out of libraries (so +downstream resolves fresh). `cargo update -p some-crate` bumps one +dependency deliberately instead of everything at once. + +### See also: + +[Dependencies](deps.md), [Conventions](conventions.md), and [the Cargo +Book on workspaces][workspaces]. + +[workspaces]: https://doc.rust-lang.org/cargo/reference/workspaces.html + +### Exercise: Add an optional feature + +Task: Add an opt-in `json` feature that pulls in `serde_json` only when requested. + +
Hint + +Marking a dependency optional creates a same-named feature automatically, which other settings can then build on. + +
+ +
Solution + +```toml +[dependencies] +serde_json = { version = "1", optional = true } + +[features] +default = [] +# Enables `serde_json` only for users who ask for `--features json`. +json = ["dep:serde_json"] +``` + +and gate the code behind the feature: + +```rust,ignore +#[cfg(feature = "json")] +fn export_json() -> String { + serde_json::to_string(&42).unwrap() +} + +#[cfg(not(feature = "json"))] +fn export_json() -> String { + "json support not compiled in".to_string() +} +``` +
diff --git a/src/crates/using_lib.md b/src/crates/using_lib.md index 30b18125fb..62f674331b 100644 --- a/src/crates/using_lib.md +++ b/src/crates/using_lib.md @@ -5,8 +5,6 @@ of its items will then be imported under a module named the same as the library. This module generally behaves the same way as any other module. ```rust,ignore -// extern crate rary; // May be required for Rust 2015 edition or earlier - fn main() { rary::public_function(); diff --git a/src/custom_types/enum/testcase_linked_list.md b/src/custom_types/enum/testcase_linked_list.md index 7a33a246e4..c06e411aee 100644 --- a/src/custom_types/enum/testcase_linked_list.md +++ b/src/custom_types/enum/testcase_linked_list.md @@ -75,6 +75,65 @@ fn main() { } ``` +### Exercise: Return the tail and the length + +Task: Add a `tail` method returning the list after the head, and verify it with `len`. + +
Hint + +Borrowing the node lets you match on its shape and hand back a reference to what follows the head. + +
+ +
Solution + +```rust,editable +use crate::List::*; + +enum List { + Cons(u32, Box), + Nil, +} + +impl List { + fn new() -> List { + Nil + } + + fn prepend(self, elem: u32) -> List { + Cons(elem, Box::new(self)) + } + + fn len(&self) -> u32 { + match *self { + Cons(_, ref tail) => 1 + tail.len(), + Nil => 0 + } + } + + fn tail(&self) -> Option<&List> { + match *self { + Cons(_, ref tail) => Some(tail), + Nil => None, + } + } +} + +fn main() { + let mut list = List::new(); + list = list.prepend(1); + list = list.prepend(2); + list = list.prepend(3); + + assert_eq!(list.len(), 3); + let tail = list.tail().expect("non-empty list has a tail"); + assert_eq!(tail.len(), 2); + println!("tail length: {}", tail.len()); +} +``` + +
+ ### See also: [`Box`][box] and [methods][methods] diff --git a/src/ecosystem.md b/src/ecosystem.md new file mode 100644 index 0000000000..ac8f4b2afa --- /dev/null +++ b/src/ecosystem.md @@ -0,0 +1,7 @@ +# Ecosystem + +The standard library ends where the ecosystem begins. Most applied +Rust is a thin layer of your logic over community crates: argument +parsing, serialization, async runtimes, and error reporting. The +[walkthrough](ecosystem/cli_json.md) combines two of them into one +small program; the chapters it builds on are linked from there. diff --git a/src/ecosystem/cli_json.md b/src/ecosystem/cli_json.md new file mode 100644 index 0000000000..b7ecd803f0 --- /dev/null +++ b/src/ecosystem/cli_json.md @@ -0,0 +1,137 @@ +# CLI + JSON walkthrough + +This page combines [`clap`][clap] (argument parsing via derive) with +[`serde_json`][serde_json] (serialization) into one applied program: a +greeter CLI that prints its greeting as JSON. It builds on [Procedural +macros][proc] for the derives and [Error +reporting][reporting] for the `anyhow` error handling. + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: clap = { version = "4", features = ["derive"] } +// Cargo.toml: serde = { version = "1", features = ["derive"] } +// Cargo.toml: serde_json = "1" +// Cargo.toml: anyhow = "1" + +use anyhow::{Context, Result}; +use clap::Parser; +use serde::{Deserialize, Serialize}; + +/// Greet someone and print the greeting as JSON. +#[derive(Parser, Debug)] +#[command(name = "greeter", version, about)] +struct Args { + /// Who to greet. + #[arg(long, default_value = "world")] + name: String, + + /// Uppercase the greeting. + #[arg(long)] + shout: bool, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct Greeting { + message: String, +} + +fn greet(args: &Args) -> Greeting { + let mut message = format!("hello {}", args.name); + if args.shout { + message.make_ascii_uppercase(); + } + Greeting { message } +} + +fn main() -> Result<()> { + let args = Args::parse(); + let greeting = greet(&args); + + // Round-trip through JSON to prove the shape survives serialization. + let json = serde_json::to_string(&greeting)?; + let back: Greeting = serde_json::from_str(&json) + .context("serializing the greeting produced invalid JSON")?; + assert_eq!(greeting, back); + + println!("{}", json); + Ok(()) +} +``` + +```shell +$ cargo run -- --name ferris +{"message":"hello ferris"} +$ cargo run -- --name ferris --shout +{"message":"HELLO FERRIS"} +``` + +`#[derive(Parser)]` generates the `--help` text, validation, and +`--version` from the struct definition, so adding a flag is adding a +field. `#[derive(Serialize, Deserialize)]` does the same for the JSON +shape: rename a field once and both directions follow. + +### See also: + +[Procedural macros][proc], [Error reporting][reporting], and +[Workspaces][workspaces]. + +[proc]: ../macros/proc.md +[reporting]: ../error/reporting.md +[workspaces]: ../cargo/workspaces.md +[clap]: https://docs.rs/clap/latest/clap/ +[serde_json]: https://docs.rs/serde_json/latest/serde_json/ + +### Exercise: Add a repeat flag + +Task: Add a `--repeat N` flag that prints the greeting JSON N times, one per line. + +
Hint + +A numeric field with a default behaves like the existing name field, and the print can move into a counted loop. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: clap = { version = "4", features = ["derive"] } +// Cargo.toml: serde = { version = "1", features = ["derive"] } +// Cargo.toml: serde_json = "1" +// Cargo.toml: anyhow = "1" + +use anyhow::Result; +use clap::Parser; +use serde::{Deserialize, Serialize}; + +#[derive(Parser, Debug)] +#[command(name = "greeter", version, about)] +struct Args { + #[arg(long, default_value = "world")] + name: String, + + #[arg(long)] + shout: bool, + + /// How many times to print the greeting. + #[arg(long, default_value_t = 1)] + repeat: u32, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct Greeting { + message: String, +} + +fn main() -> Result<()> { + let args = Args::parse(); + let mut message = format!("hello {}", args.name); + if args.shout { + message.make_ascii_uppercase(); + } + let json = serde_json::to_string(&Greeting { message })?; + for _ in 0..args.repeat { + println!("{}", json); + } + Ok(()) +} +``` +
diff --git a/src/error/reporting.md b/src/error/reporting.md new file mode 100644 index 0000000000..7ef5bcefec --- /dev/null +++ b/src/error/reporting.md @@ -0,0 +1,116 @@ +# Error reporting with `anyhow` and `thiserror` + +Hand-written error enums (like [`DoubleError`][double]) teach the +mechanics, but production code splits the job in two: libraries define +errors with [`thiserror`][thiserror], binaries report them with +[`anyhow`][anyhow]. Both are `ignore` examples — add the `Cargo.toml` +line and run them locally. + +A library derives `thiserror::Error` instead of writing `Display` and +`From` by hand. Each variant documents its own message, and `#[from]` +generates the conversion from an underlying error: + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: thiserror = "2" + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ParseDoubleError { + #[error("please use a vector with at least one element")] + EmptyVec, + #[error("could not parse the first element: {0}")] + BadParse(#[from] std::num::ParseIntError), +} + +fn double_first(vec: Vec<&str>) -> Result { + let first = vec.first().ok_or(ParseDoubleError::EmptyVec)?; + Ok(2 * first.parse::()?) +} + +fn main() { + println!("{:?}", double_first(vec!["42"])); + println!("{:?}", double_first(vec![])); + println!("{:?}", double_first(vec!["tofu"])); +} +``` + +A binary returns `anyhow::Result` and adds context at each layer with +`.context()`. The printed report then reads like a backtrace of *what +the program was trying to do*, not just the low-level failure: + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: anyhow = "1" + +use anyhow::{Context, Result}; + +fn double_first(vec: Vec<&str>) -> Result { + let first = vec + .first() + .context("expected at least one number on the command line")?; + let n: i32 = first + .parse() + .with_context(|| format!("could not parse '{}' as a number", first))?; + Ok(2 * n) +} + +fn main() -> Result<()> { + println!("doubled: {}", double_first(vec!["21"])?); + Ok(()) +} +``` + +Rule of thumb: `thiserror` for errors you *define* (libraries), +`anyhow` for errors you *propagate and report* (binaries and tests). + +### See also: + +[Defining an error type][double], [`thiserror`][thiserror], and +[`anyhow`][anyhow]. + +[double]: multiple_error_types/define_error_type.md +[thiserror]: https://docs.rs/thiserror/latest/thiserror/ +[anyhow]: https://docs.rs/anyhow/latest/anyhow/ + +### Exercise: Derive the hand-written error + +Task: Convert the hand-written `DoubleError` into a `thiserror` enum and compare line counts. + +
Hint + +One attribute on each variant replaces a manual trait implementation and its conversions. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: thiserror = "2" + +use thiserror::Error; + +// Three lines replace the struct plus the manual `Display` impl, +// and `#[from]` adds `ParseIntError` conversion for free. +#[derive(Error, Debug)] +pub enum DoubleError { + #[error("invalid first item to double")] + InvalidFirst, + #[error("unparsable number: {0}")] + BadParse(#[from] std::num::ParseIntError), +} + +fn double_first(vec: Vec<&str>) -> Result { + let first = vec.first().ok_or(DoubleError::InvalidFirst)?; + Ok(2 * first.parse::()?) +} + +fn main() { + for input in [vec!["42"], vec![], vec!["tofu"]] { + match double_first(input) { + Ok(n) => println!("The first doubled is {}", n), + Err(e) => println!("Error: {}", e), + } + } +} +``` +
diff --git a/src/error/result/enter_question_mark.md b/src/error/result/enter_question_mark.md index 49d12760ea..a287194f57 100644 --- a/src/error/result/enter_question_mark.md +++ b/src/error/result/enter_question_mark.md @@ -38,36 +38,9 @@ fn main() { ## The `try!` macro -Before there was `?`, the same functionality was achieved with the `try!` macro. -The `?` operator is now recommended, but you may still find `try!` when looking -at older code. The same `multiply` function from the previous example -would look like this using `try!`: - -```rust,editable,edition2015 -// To compile and run this example without errors, while using Cargo, change the value -// of the `edition` field, in the `[package]` section of the `Cargo.toml` file, to "2015". - -use std::num::ParseIntError; - -fn multiply(first_number_str: &str, second_number_str: &str) -> Result { - let first_number = try!(first_number_str.parse::()); - let second_number = try!(second_number_str.parse::()); - - Ok(first_number * second_number) -} - -fn print(result: Result) { - match result { - Ok(n) => println!("n is {}", n), - Err(e) => println!("Error: {}", e), - } -} - -fn main() { - print(multiply("10", "2")); - print(multiply("t", "2")); -} -``` +Before there was `?`, pre-2017 code used the `try!` macro for the same +purpose. The `?` operator replaced it and is what you should use and +look for in modern code; there is no runnable example. [^†]: See [re-enter ?][re_enter_?] for more details. diff --git a/src/generics/bounds/testcase_empty.md b/src/generics/bounds/testcase_empty.md index e956a57e42..1bd55782d2 100644 --- a/src/generics/bounds/testcase_empty.md +++ b/src/generics/bounds/testcase_empty.md @@ -34,6 +34,44 @@ fn main() { } ``` +### Exercise: Add a third empty bound + +Task: Add a new empty trait, implement it for one type, and bound a function on it. + +
Hint + +The new trait follows the same shape as the existing two, and only the implementing type satisfies the new bound. + +
+ +
Solution + +```rust,editable +struct Cardinal; +struct BlueJay; +struct Turkey; + +trait Red {} +trait Blue {} +trait Green {} + +impl Red for Cardinal {} +impl Blue for BlueJay {} +impl Green for Turkey {} + +fn red(_: &T) -> &'static str { "red" } +fn blue(_: &T) -> &'static str { "blue" } +fn green(_: &T) -> &'static str { "green" } + +fn main() { + let turkey = Turkey; + assert_eq!(green(&turkey), "green"); + println!("A turkey is {}", green(&turkey)); +} +``` + +
+ ### See also: [`std::cmp::Eq`][eq], [`std::marker::Copy`][copy], and [`trait`s][traits] diff --git a/src/generics/const_generics.md b/src/generics/const_generics.md new file mode 100644 index 0000000000..d5083c084e --- /dev/null +++ b/src/generics/const_generics.md @@ -0,0 +1,69 @@ +# Const generics + +Types and functions can be parameterized by constant values — most +often array lengths. `struct Matrix` is a different +type for each `N`, and `fn sum(a: [i32; N])` accepts an +array of any length, all checked at compile time. + +```rust,editable +use std::fmt::Debug; + +#[derive(Debug)] +struct Matrix { + rows: [[f64; N]; N], +} + +impl Matrix { + fn zeros() -> Self { + Matrix { rows: [[0.0; N]; N] } + } +} + +fn sum(values: [i32; N]) -> i32 { + values.iter().sum() +} + +fn main() { + let m = Matrix::<2>::zeros(); + println!("{:?}", m); + println!("sum3: {}", sum([1, 2, 3])); + println!("sum5: {}", sum([1, 2, 3, 4, 5])); +} +``` + +Const generics are still more limited than type generics: expressions +involving generic constants (like `[0; N + 1]`) are not allowed on +stable, and most trait bounds on const parameters are unstable. When +you hit those walls, a macro or a `Vec` is the pragmatic fallback. + +### See also: + +[Const Generics in the Reference][ref] and [Arrays][arrays]. + +[ref]: https://doc.rust-lang.org/reference/items/generics.html#const-generics +[arrays]: ../primitives/array.md + +### Exercise: Generalize a dot product + +Task: Generalize this three-element dot product to arrays of any length. + +
Hint + +The length can become a parameter of the function, letting the two array arguments share it. + +
+ +
Solution + +```rust,editable +fn dot(a: [i32; N], b: [i32; N]) -> i32 { + a.iter().zip(b.iter()).map(|(x, y)| x * y).sum() +} + +fn main() { + assert_eq!(dot([1, 2, 3], [4, 5, 6]), 32); + assert_eq!(dot([1, 2], [3, 4]), 11); + println!("dot products check out"); +} +``` +
diff --git a/src/generics/gats.md b/src/generics/gats.md new file mode 100644 index 0000000000..81fc8cf8a8 --- /dev/null +++ b/src/generics/gats.md @@ -0,0 +1,97 @@ +# Generic Associated Types + +An associated type can itself be generic: `type Item<'a>` declares a +family of types, one per lifetime. Stable since 1.65, Generic Associated +Types (GATs) express *lending* patterns — iterators that hand out +references tied to the iterator itself, which plain associated types +cannot name. + +```rust,editable +// Each call to `next` lends out data for as long as the borrow lasts. +trait LendingIterator { + type Item<'a> where Self: 'a; + + fn next(&mut self) -> Option>; +} + +// Yields sliding windows into a slice. +struct Windows<'s> { + slice: &'s [u32], + size: usize, + pos: usize, +} + +impl<'s> LendingIterator for Windows<'s> { + type Item<'a> where Self: 'a = &'a [u32]; + + fn next(&mut self) -> Option> { + let window = self.slice.get(self.pos..self.pos + self.size)?; + self.pos += 1; + Some(window) + } +} +fn main() { + let data = vec![1, 2, 3, 4]; + let mut windows = Windows { slice: &data, size: 2, pos: 0 }; + while let Some(w) = windows.next() { + println!("{:?}", w); + } +} +``` + +### See also: + +[Associated types][assoc], [Lifetimes][lifetimes], and [Iterators][iter]. + +[assoc]: ../generics/assoc_items/types.md +[lifetimes]: ../scope/lifetime.md +[iter]: ../trait/iter.md + +### Exercise: Lend lines of a string + +Task: Implement `LendingIterator` for a type that yields one line at a time. + +
Hint + +One iterator method on string slices already splits off the next line and reports what remains. + +
+ +
Solution + +```rust,editable +trait LendingIterator { + type Item<'a> where Self: 'a; + + fn next(&mut self) -> Option>; +} + +struct Lines<'s> { + rest: &'s str, +} + +impl<'s> LendingIterator for Lines<'s> { + type Item<'a> where Self: 'a = &'a str; + + fn next(&mut self) -> Option> { + if self.rest.is_empty() { + return None; + } + let (line, rest) = match self.rest.split_once('\n') { + Some((line, rest)) => (line, rest), + None => (self.rest, ""), + }; + self.rest = rest; + Some(line) + } +} + +fn main() { + let text = "one\ntwo\nthree"; + let mut lines = Lines { rest: text }; + while let Some(line) = lines.next() { + println!("line: {}", line); + } +} +``` +
diff --git a/src/generics/phantom/testcase_units.md b/src/generics/phantom/testcase_units.md index c3b9cbd2e1..18162aef0d 100644 --- a/src/generics/phantom/testcase_units.md +++ b/src/generics/phantom/testcase_units.md @@ -72,6 +72,55 @@ fn main() { } ``` +### Exercise: Subtract lengths + +Task: Implement `Sub` for `Length` mirroring the `Add` implementation. + +
Hint + +The trait shape matches addition, with the inner operator swapped for its opposite. + +
+ +
Solution + +```rust,editable +use std::ops::{Add, Sub}; +use std::marker::PhantomData; + +#[derive(Debug, Clone, Copy)] +enum Inch {} + +#[derive(Debug, Clone, Copy)] +struct Length(f64, PhantomData); + +impl Add for Length { + type Output = Length; + + fn add(self, rhs: Length) -> Length { + Length(self.0 + rhs.0, PhantomData) + } +} + +impl Sub for Length { + type Output = Length; + + fn sub(self, rhs: Length) -> Length { + Length(self.0 - rhs.0, PhantomData) + } +} + +fn main() { + let foot: Length = Length(12.0, PhantomData); + let three_inches: Length = Length(3.0, PhantomData); + let nine_inches = foot - three_inches; + assert_eq!(nine_inches.0, 9.0); + println!("12in - 3in = {:?} in", nine_inches.0); +} +``` + +
+ ### See also: [Borrowing (`&`)], [Bounds (`X: Y`)], [enum], [impl & self], diff --git a/src/hello/print/print_display/testcase_list.md b/src/hello/print/print_display/testcase_list.md index 095e454fa0..51b6a8f707 100644 --- a/src/hello/print/print_display/testcase_list.md +++ b/src/hello/print/print_display/testcase_list.md @@ -58,6 +58,45 @@ printed. The new output should look like this: [0: 1, 1: 2, 2: 3] ``` +### Exercise: Print each element with its index + +Task: Change the `Display` impl so each element prints with its index. + +
Hint + +The loop already tracks the position, so only the formatting of each element needs to change. + +
+ +
Solution + +```rust,editable +use std::fmt; + +struct List(Vec); + +impl fmt::Display for List { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let vec = &self.0; + + write!(f, "[")?; + + for (index, v) in vec.iter().enumerate() { + if index != 0 { write!(f, ", ")?; } + write!(f, "{}: {}", index, v)?; + } + + write!(f, "]") + } +} + +fn main() { + let v = List(vec![1, 2, 3]); + assert_eq!(v.to_string(), "[0: 1, 1: 2, 2: 3]"); + println!("{}", v); +} +``` +
### See also: [`for`][for], [`ref`][ref], [`Result`][result], [`struct`][struct], diff --git a/src/index.md b/src/index.md index 51216fc36a..53911fb55e 100644 --- a/src/index.md +++ b/src/index.md @@ -47,9 +47,11 @@ Now let's begin! - [Error handling](error.md) - Learn Rust way of handling failures. -- [Std library types](std.md) - Learn about some custom types provided by `std` library. +- [Std library types](std.md) - Learn about some custom types provided by `std` library, including `Cell`, `OnceLock`, `Cow`, more collections, and iterators. -- [Std misc](std_misc.md) - More custom types for file handling, threads. +- [Std misc](std_misc.md) - More custom types for file handling, threads, and shared state. + +- [Async](async.md) - Learn about asynchronous programming with futures, tasks, and message streams. - [Testing](testing.md) - All sorts of testing in Rust. @@ -57,6 +59,8 @@ Now let's begin! - [Compatibility](compatibility.md) - Handling Rust's evolution and potential compatibility issues. +- [Ecosystem](ecosystem.md) - Combine community crates into an applied CLI program. + - [Meta](meta.md) - Documentation, Benchmarking. [rust]: https://www.rust-lang.org/ diff --git a/src/macros/proc.md b/src/macros/proc.md new file mode 100644 index 0000000000..c4cf580e8e --- /dev/null +++ b/src/macros/proc.md @@ -0,0 +1,95 @@ +# Procedural macros + +`macro_rules!` patterns match syntax, but some code generation needs +the full power of Rust code: reading struct fields, deriving trait +implementations, or generating new items from attributes. Procedural +macros do that — they are functions from token stream to token stream +that run at compile time. There are three kinds: + +* **Derive macros** add `#[derive(...)]` implementations, e.g. + `#[derive(Debug)]` or `serde`'s `Serialize`. +* **Attribute macros** define new attributes like `#[tokio::main]` + that transform the annotated item. +* **Function-like macros** look like function calls, e.g. + `sql!(SELECT * FROM posts)`, taking tokens and expanding to code. + +The canonical derive example is serialization: one attribute generates +correct, field-by-field conversion code you would otherwise hand-write +and drift out of sync: + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: serde = { version = "1", features = ["derive"] } +// Cargo.toml: serde_json = "1" + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct Point { + x: i32, + y: i32, +} + +fn main() -> serde_json::Result<()> { + let point = Point { x: 1, y: 2 }; + let json = serde_json::to_string(&point)?; + println!("serialized: {}", json); + + let back: Point = serde_json::from_str(&json)?; + println!("round-trip: {:?}", back); + Ok(()) +} +``` + +When to use which: reach for `macro_rules!` for syntax sugar inside +your crate (compact repetition, small DSLs); reach for a proc-macro +when you generate trait impls from type structure or transform +annotated items. Writing a new proc-macro means a separate crate with +`proc-macro = true` — start from an established framework crate rather +than raw token manipulation. + +### See also: + +[`macro_rules!`][macros], [`serde`][serde], and [the Procedural Macros +chapter of the Reference][ref]. + +[macros]: ../macros.md +[serde]: https://serde.rs/derive.html +[ref]: https://doc.rust-lang.org/reference/procedural-macros.html + +### Exercise: Derive instead of hand-writing + +Task: Replace a hand-written `Display` impl on a three-field struct with a derived serialization. + +
Hint + +One attribute on the struct generates the conversion, so the manual formatting code can go away entirely. + +
+ +
Solution + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: serde = { version = "1", features = ["derive"] } +// Cargo.toml: serde_json = "1" + +use serde::Serialize; + +// No manual `Display` needed: the derived impl tracks the fields. +#[derive(Serialize, Debug)] +struct Config { + host: String, + port: u16, + tls: bool, +} + +fn main() -> serde_json::Result<()> { + let config = Config { + host: "localhost".to_string(), + port: 8080, + tls: false, + }; + println!("{}", serde_json::to_string_pretty(&config)?); + Ok(()) +} +``` +
diff --git a/src/scope/borrow.md b/src/scope/borrow.md index 196cf079f4..8824e899d3 100644 --- a/src/scope/borrow.md +++ b/src/scope/borrow.md @@ -49,3 +49,44 @@ fn main() { eat_box_i32(boxed_i32); } ``` + +### Exercise: Borrow, then destroy + +Task: Restructure the example above so the box is borrowed and then destroyed, without deleting any call. + +
Hint + +A borrow ends at its last use, so destroying the box after that point satisfies the checker. + +
+ +
Solution + +```rust,editable +fn eat_box_i32(boxed_i32: Box) { + println!("Destroying box that contains {}", boxed_i32); +} + +fn borrow_i32(borrowed_i32: &i32) { + println!("This int is: {}", borrowed_i32); +} + +fn main() { + let boxed_i32 = Box::new(5_i32); + let stacked_i32 = 6_i32; + + borrow_i32(&boxed_i32); + borrow_i32(&stacked_i32); + + { + let ref_to_i32: &i32 = &boxed_i32; + borrow_i32(ref_to_i32); + // The borrow ends here, at its last use. + } + + // Nothing is borrowed anymore, so destroying is allowed. + eat_box_i32(boxed_i32); +} +``` + +
diff --git a/src/scope/lifetime.md b/src/scope/lifetime.md index 1b44ed1698..96f53490a1 100644 --- a/src/scope/lifetime.md +++ b/src/scope/lifetime.md @@ -40,3 +40,32 @@ fn main() { Note that no names or types are assigned to label lifetimes. This restricts how lifetimes will be able to be used as we will see. + +### Exercise: Relate two borrows with one lifetime + +Task: Write a `longest` function returning the longer of two string slices. + +
Hint + +Elided lifetimes give each input its own unrelated lifetime, so name one lifetime explicitly and use it for both inputs and the output. + +
+ +
Solution + +```rust,editable +// Both inputs and the output share `'a`: the result lives exactly as +// long as the shorter-lived input, which the compiler checks. +fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { + if a.len() >= b.len() { a } else { b } +} + +fn main() { + let short = "hi"; + let long = "hello, world"; + assert_eq!(longest(short, long), "hello, world"); + println!("longest: {}", longest(short, long)); +} +``` + +
diff --git a/src/scope/move.md b/src/scope/move.md index ec829150ee..da5919cc7f 100644 --- a/src/scope/move.md +++ b/src/scope/move.md @@ -58,3 +58,35 @@ fn main() { ``` [references]: ../flow_control/match/destructuring/destructure_pointers.md + +### Exercise: Lend instead of moving + +Task: Change `destroy_box` to borrow its argument so `b` stays usable afterwards. + +
Hint + +Receiving a reference instead of ownership leaves the owner untouched, and the call site lends with one character. + +
+ +
Solution + +```rust,editable +// This function borrows the heap allocated memory instead of taking it. +fn inspect_box(c: &Box) { + println!("Inspecting a box that contains {}", c); +} + +fn main() { + let a = Box::new(5i32); + + // *Move* `a` into `b`; `a` can no longer be used. + let b = a; + + // Lend `b` instead of moving it, so it stays usable below. + inspect_box(&b); + println!("b still contains: {}", b); +} +``` + +
diff --git a/src/std/cell.md b/src/std/cell.md new file mode 100644 index 0000000000..ccee2ef212 --- /dev/null +++ b/src/std/cell.md @@ -0,0 +1,95 @@ +# `Cell` and `RefCell` + +Most borrow rules are checked at compile time, but `Cell` and `RefCell` +move the check for shared references to run time. This allows mutation +through a shared (`&`) reference, which is useful for types that need +interior mutability, such as when a method taking `&self` must update a +cache or counter. + +`Cell` stores a value of a type that implements `Copy`. It has no +borrowing: `get` returns a copy and `set` replaces the value. + +```rust,editable +use std::cell::Cell; + +fn main() { + let counter = Cell::new(0); + + // `&counter` is shared, yet the value can still change. + let shared: &Cell = &counter; + shared.set(shared.get() + 1); + shared.set(shared.get() + 1); + + println!("counter: {}", counter.get()); +} +``` + +`RefCell` works for any type, but enforces the borrow rules at run +time instead: either many shared borrows (`borrow`) or one exclusive +borrow (`borrow_mut`), never both at once. Breaking the rule panics +instead of failing to compile, so keep `borrow_mut` scopes short. + +```rust,editable +use std::cell::RefCell; + +fn main() { + let log = RefCell::new(Vec::new()); + + log.borrow_mut().push("first"); + log.borrow_mut().push("second"); + + // Many shared borrows at once are fine. + let first = log.borrow(); + println!("entries: {}, first: {}", first.len(), first[0]); +} +``` + +`Ref::map` derives a borrow of part of the contents without copying, +keeping the original borrow alive: + +```rust,editable +use std::cell::{Ref, RefCell}; + +fn main() { + let pair = RefCell::new(("ferris".to_string(), 42)); + let borrowed = pair.borrow(); + let name: Ref = Ref::map(borrowed, |(name, _)| name); + println!("name: {}", name); +} +``` + +### See also: + +[`std::cell`][cell], [`Cell`][cell_struct], and [`RefCell`][refcell]. + +[cell]: https://doc.rust-lang.org/std/cell/index.html +[cell_struct]: https://doc.rust-lang.org/std/cell/struct.Cell.html +[refcell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html + +### Exercise: Fix a double borrow panic + +Task: Fix the program so both pushes succeed without panicking. + +
Hint + +Two exclusive borrows of the same value cannot overlap; end each one before starting the next. + +
+ +
Solution + +```rust,editable +use std::cell::RefCell; + +fn main() { + let log = RefCell::new(Vec::new()); + + // Each `borrow_mut` temporary drops at the end of its statement, + // so the two exclusive borrows never overlap. + log.borrow_mut().push("first"); + log.borrow_mut().push("second"); + + println!("entries: {:?}", log.borrow()); +} +``` +
diff --git a/src/std/collections.md b/src/std/collections.md new file mode 100644 index 0000000000..44cc825388 --- /dev/null +++ b/src/std/collections.md @@ -0,0 +1,100 @@ +# More collections + +Beyond `Vec`, `String`, and `HashMap`, the standard library ships +ordered and double-ended containers that cover most everyday needs. + +`BTreeMap` and `BTreeSet` keep keys sorted, so iteration order is +deterministic. `VecDeque` pushes and pops cheaply from both ends, and +`BinaryHeap` is a max-heap that yields the largest element first. + +```rust,editable +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, VecDeque}; + +fn main() { + // Sorted by key: apple, cherry, pear. + let mut scores = BTreeMap::new(); + scores.insert("pear", 3); + scores.insert("apple", 5); + scores.insert("cherry", 4); + for (fruit, score) in &scores { + println!("{fruit}: {score}"); + } + + let mut seen = BTreeSet::new(); + seen.insert(3); + seen.insert(1); + seen.insert(2); + println!("sorted set: {:?}", seen); + + let mut queue = VecDeque::new(); + queue.push_back("middle"); + queue.push_front("first"); + queue.push_back("last"); + println!("front: {:?}", queue.pop_front()); + + let mut heap = BinaryHeap::new(); + heap.push(1); + heap.push(5); + heap.push(2); + println!("largest: {:?}", heap.pop()); +} +``` + +For counting and caches, `HashMap::entry` inserts a default only when +the key is missing, then hands back a mutable reference in one lookup: + +```rust,editable +use std::collections::HashMap; + +fn main() { + let mut pages = HashMap::new(); + // Insert the default `0` for a new key, then add one visit. + *pages.entry("index").or_insert(0) += 1; + *pages.entry("index").or_insert(0) += 1; + *pages.entry("about").or_insert(0) += 1; + + println!("visits: {:?}", pages); +} +``` + +### See also: + +[`std::collections`][collections], [`HashMap::entry`][entry], +[`BTreeMap`][btreemap], [`VecDeque`][vecdeque], and [`BinaryHeap`][heap]. + +[collections]: https://doc.rust-lang.org/std/collections/index.html +[entry]: https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html +[btreemap]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html +[vecdeque]: https://doc.rust-lang.org/std/collections/struct.VecDeque.html +[heap]: https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html + +### Exercise: Count words with entry + +Task: Count how often each word appears in a sentence using the `entry` API. + +
Hint + +Look up each word once per occurrence and bump a default of zero held behind the entry. + +
+ +
Solution + +```rust,editable +use std::collections::HashMap; + +fn main() { + let sentence = "the quick brown fox jumps over the lazy fox"; + let mut counts: HashMap<&str, usize> = HashMap::new(); + + for word in sentence.split_whitespace() { + *counts.entry(word).or_insert(0) += 1; + } + + assert_eq!(counts["the"], 2); + assert_eq!(counts["fox"], 2); + assert_eq!(counts["quick"], 1); + println!("counts: {:?}", counts); +} +``` +
diff --git a/src/std/cow.md b/src/std/cow.md new file mode 100644 index 0000000000..0b679774cd --- /dev/null +++ b/src/std/cow.md @@ -0,0 +1,80 @@ +# `Cow` + +`Cow<'_, T>` (clone-on-write) holds either borrowed data or an owned +value of the same shape. Functions taking `Cow` accept both `&str` +(without allocating) and `String` (without conversion), and only clone +when mutation is actually needed. + +```rust,editable +use std::borrow::Cow; + +fn main() { + // Borrowed: no allocation, just a reference wrapper. + let borrowed: Cow = Cow::Borrowed("hello"); + // Owned: takes over an existing allocation. + let owned: Cow = Cow::Owned("hello".to_string()); + + println!("borrowed: {}, owned: {}", borrowed, owned); + + match borrowed { + Cow::Borrowed(s) => println!("still borrowed: {}", s), + Cow::Owned(_) => println!("unexpectedly owned"), + } +} +``` + +`to_mut` clones the data on first write if it was borrowed, then hands +out a mutable reference. `into_owned` consumes the `Cow` and returns an +owned value, cloning only in the borrowed case. + +```rust,editable +use std::borrow::Cow; + +fn shout(mut text: Cow) -> String { + // Borrows clone here; already-owned values mutate in place. + text.to_mut().make_ascii_uppercase(); + text.into_owned() +} + +fn main() { + println!("{}", shout(Cow::Borrowed("hello"))); + println!("{}", shout(Cow::Owned("world".to_string()))); +} +``` + +### See also: + +[`std::borrow::Cow`][cow] and [`ToOwned`][toowned]. + +[cow]: https://doc.rust-lang.org/std/borrow/enum.Cow.html +[toowned]: https://doc.rust-lang.org/std/borrow/trait.ToOwned.html + +### Exercise: Describe borrowed or owned input + +Task: Write a function that reports whether a `Cow` is borrowed or owned. + +
Hint + +Matching on the two variants tells you which case you hold without moving the contents. + +
+ +
Solution + +```rust,editable +use std::borrow::Cow; + +fn describe(c: &Cow) -> &'static str { + match c { + Cow::Borrowed(_) => "borrowed", + Cow::Owned(_) => "owned", + } +} + +fn main() { + let a: Cow = Cow::Borrowed("hi"); + let b: Cow = Cow::Owned("hi".to_string()); + println!("a is {}, b is {}", describe(&a), describe(&b)); +} +``` +
diff --git a/src/std/hash.md b/src/std/hash.md index 4baac9b788..ece240769b 100644 --- a/src/std/hash.md +++ b/src/std/hash.md @@ -62,3 +62,35 @@ For more information on how hashing and hash maps [Hash Table Wikipedia][wiki-hash] [wiki-hash]: https://en.wikipedia.org/wiki/Hash_table + +### Exercise: Insert only if missing + +Task: Add a fallback number for a missing contact with `entry`, without overwriting existing entries. + +
Hint + +One lookup both checks for the key and inserts the default, leaving present values untouched. + +
+ +
Solution + +```rust,editable +use std::collections::HashMap; + +fn main() { + let mut contacts = HashMap::new(); + contacts.insert("Daniel", "798-1364"); + + // Ashley is missing, so the fallback is inserted. + contacts.entry("Ashley").or_insert("645-7689"); + // Daniel is present, so his number is kept as is. + contacts.entry("Daniel").or_insert("000-0000"); + + assert_eq!(contacts["Daniel"], "798-1364"); + assert_eq!(contacts["Ashley"], "645-7689"); + println!("contacts: {:?}", contacts); +} +``` + +
diff --git a/src/std/iter.md b/src/std/iter.md new file mode 100644 index 0000000000..0a527e5ae3 --- /dev/null +++ b/src/std/iter.md @@ -0,0 +1,103 @@ +# Iterators in depth + +Iterator *adapters* (`map`, `filter`, `take`, `zip`) transform lazily and +return a new iterator, while *consumers* (`collect`, `sum`, `fold`, +`for_each`) drive the iterator to completion and produce a final value. +Nothing runs until a consumer pulls: adapters without a consumer do no +work at all. + +```rust,editable +fn main() { + let numbers = vec![1, 2, 3, 4, 5, 6]; + + // Adapters chain lazily; `collect` consumes the chain into a Vec. + let squares: Vec<_> = numbers.iter().map(|n| n * n).collect(); + println!("squares: {:?}", squares); + + // `fold` consumes with an accumulator instead of allocating. + let total: i32 = numbers.iter().filter(|n| *n % 2 == 0).sum(); + println!("even sum: {total}"); + + let product = numbers.iter().fold(1, |acc, n| acc * n); + println!("product: {product}"); +} +``` + +`collect` can also short-circuit: gathering into `Result, _>` +stops at the first error and returns it, instead of a partial vector. + +```rust,editable +fn main() { + let words = vec!["10", "20", "oops", "30"]; + + // Parsing stops at "oops"; the numbers before it are discarded. + let parsed: Result, _> = + words.iter().map(|w| w.parse()).collect(); + println!("parsed: {:?}", parsed); + + let good = vec!["1", "2", "3"]; + let ok: Result, _> = + good.iter().map(|w| w.parse()).collect(); + println!("ok: {:?}", ok); +} +``` + +How you borrow the collection matters: + +| Form | What it yields | Collection afterwards | +|---|---|---| +| `iter()` | `&T` | Borrowed, still usable | +| `iter_mut()` | `&mut T` | Mutably borrowed, still usable | +| `into_iter()` | `T` | Moved (or borrowed for `&Vec`), often consumed | + +```rust,editable +fn main() { + let mut values = vec![1, 2, 3]; + + for v in values.iter_mut() { + *v *= 10; + } + // `iter_mut` only borrowed, so the vector is still ours. + println!("scaled: {:?}", values); + + let owned_sum: i32 = values.into_iter().sum(); + // `values` was moved and can no longer be used here. + println!("sum: {owned_sum}"); +} +``` + +### See also: + +[`std::iter::Iterator`][iter], [`collect`][collect], and +[`Iterator::fold`][fold]. + +[iter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html +[collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect +[fold]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold + +### Exercise: Fix a consumed iterator + +Task: Fix the program so the filtered list prints and the count is correct. + +
Hint + +One consumer exhausts the iterator, so a second consumer needs its own iterator over the same data. + +
+ +
Solution + +```rust,editable +fn main() { + let numbers = vec![1, 2, 3, 4, 5, 6]; + + // `is_even` is a closure, so each call builds a fresh iterator. + let is_even = || numbers.iter().filter(|n| *n % 2 == 0); + + let evens: Vec<_> = is_even().copied().collect(); + let count = is_even().count(); + + println!("evens: {:?}, count: {}", evens, count); +} +``` +
diff --git a/src/std/once.md b/src/std/once.md new file mode 100644 index 0000000000..cae3c99b84 --- /dev/null +++ b/src/std/once.md @@ -0,0 +1,88 @@ +# `OnceLock` and `LazyLock` + +`OnceLock` stores a value that is initialized at most once, even when +several threads race to initialize it. `get_or_init` runs its closure +only for the winner; every caller gets a reference to the single value. + +```rust,editable +use std::sync::OnceLock; + +static CONFIG: OnceLock = OnceLock::new(); + +fn config() -> &'static str { + CONFIG.get_or_init(|| { + println!("initializing once"); + "cfg-v1".to_string() + }) +} + +fn main() { + println!("first: {}", config()); + // The message above prints only once: this call reuses the value. + println!("second: {}", config()); +} +``` + +`LazyLock` (stable since 1.80) wraps the same idea for statics: the +closure runs on first dereference, so declaration and initialization +stay in one place. + +```rust,editable +use std::sync::LazyLock; + +static GREETING: LazyLock = + LazyLock::new(|| format!("hello {}", "ferris")); + +fn main() { + // Nothing above ran the closure; this first use does. + println!("{}", *GREETING); + println!("again: {}", *GREETING); +} +``` + +### See also: + +[`std::sync::OnceLock`][oncelock] and [`std::sync::LazyLock`][lazylock]. + +[oncelock]: https://doc.rust-lang.org/std/sync/struct.OnceLock.html +[lazylock]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html + +### Exercise: Predict which threads initialize + +Task: Predict how many times the initializer prints when ten threads share one `OnceLock`, then check your answer. + +
Hint + +Only one thread can win the race to fill the cell; the rest wait and share the result. + +
+ +
Solution + +```rust,editable +use std::sync::OnceLock; +use std::thread; + +static CELL: OnceLock = OnceLock::new(); + +fn main() { + let handles: Vec<_> = (0..10) + .map(|_| { + thread::spawn(|| { + CELL.get_or_init(|| { + println!("initialized"); + "shared".to_string() + }) + }) + }) + .collect(); + + for h in handles { + h.join().unwrap(); + } + + // "initialized" prints exactly once no matter how many threads raced. + assert_eq!(CELL.get().unwrap(), "shared"); +} +``` +
diff --git a/src/std/word_freq.md b/src/std/word_freq.md new file mode 100644 index 0000000000..fed3788262 --- /dev/null +++ b/src/std/word_freq.md @@ -0,0 +1,80 @@ +# Capstone: word frequency + +This page combines [More collections](collections.md) with [Iterators +in depth](iter.md): count words with `HashMap::entry`, then rank them. +A `BTreeMap` keeps the alphabetical listing sorted for free, and a +`sort_by` on the collected pairs produces the leaderboard. + +```rust,editable +use std::collections::BTreeMap; + +fn word_freq(text: &str) -> BTreeMap<&str, usize> { + let mut freq = BTreeMap::new(); + for word in text.split_whitespace() { + *freq.entry(word).or_insert(0) += 1; + } + freq +} + +fn main() { + let text = "the quick brown fox jumps over the lazy dog the fox"; + let freq = word_freq(text); + + // `BTreeMap` iterates in key order, so the listing is alphabetical. + for (word, count) in &freq { + println!("{word}: {count}"); + } + + assert_eq!(freq["the"], 3); + assert_eq!(freq["fox"], 2); + assert_eq!(freq["dog"], 1); +} +``` + +Counting and ranking stay separate: the map owns the facts, and each +report (`for` loop, top-N, histogram) is a different consumer of the +same data. + +### See also: + +[More collections](collections.md) and [Iterators in +depth](iter.md). + +### Exercise: Report the top three words + +Task: Print the three most frequent words in descending order of count. + +
Hint + +Collect the pairs into a vector first, since maps cannot reorder themselves by value. + +
+ +
Solution + +```rust,editable +use std::collections::BTreeMap; + +fn word_freq(text: &str) -> BTreeMap<&str, usize> { + let mut freq = BTreeMap::new(); + for word in text.split_whitespace() { + *freq.entry(word).or_insert(0) += 1; + } + freq +} + +fn main() { + let text = "the quick brown fox jumps over the lazy dog the fox"; + let freq = word_freq(text); + + let mut pairs: Vec<(&str, usize)> = + freq.iter().map(|(&w, &c)| (w, c)).collect(); + pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0))); + + let top3: Vec<(&str, usize)> = pairs.into_iter().take(3).collect(); + assert_eq!(top3[0], ("the", 3)); + assert_eq!(top3[1], ("fox", 2)); + println!("top three: {:?}", top3); +} +``` +
diff --git a/src/std_misc/channels.md b/src/std_misc/channels.md index 15a735efbf..8d7a5a4848 100644 --- a/src/std_misc/channels.md +++ b/src/std_misc/channels.md @@ -53,3 +53,46 @@ fn main() { println!("{:?}", ids); } ``` + +### Exercise: Sum the thread ids + +Task: Edit the example above to sum the received ids and assert the total. + +
Hint + +Each thread sends its own number, so the collected values always add up to the same total. + +
+ +
Solution + +```rust,editable +use std::sync::mpsc; +use std::thread; + +static NTHREADS: i32 = 3; + +fn main() { + let (tx, rx) = mpsc::channel(); + + for id in 0..NTHREADS { + let thread_tx = tx.clone(); + thread::spawn(move || { + thread_tx.send(id).unwrap(); + }); + } + // The original sender is no longer needed; dropping it lets + // the channel close once every thread finishes. + drop(tx); + + let mut sum = 0; + for _ in 0..NTHREADS { + sum += rx.recv().unwrap(); + } + + assert_eq!(sum, 0 + 1 + 2); + println!("sum of ids: {}", sum); +} +``` + +
diff --git a/src/std_misc/sync.md b/src/std_misc/sync.md new file mode 100644 index 0000000000..c03039c2f7 --- /dev/null +++ b/src/std_misc/sync.md @@ -0,0 +1,125 @@ +# Shared state: `Mutex`, `RwLock`, atomics + +When threads must mutate shared data, the standard library offers three +levels of protection. `Mutex` allows one thread at a time and blocks +the rest; `RwLock` allows many concurrent readers or one writer; +atomics such as `AtomicUsize` update a single integer without locking +at all. + +`Mutex::lock` returns a guard that dereferences to the data. The lock +releases when the guard drops, so keep the critical section short. +A lock can be *poisoned* if another thread panicked while holding it; +`lock().unwrap()` is fine for examples, and production code decides +whether the guarded data is still usable. + +```rust,editable +use std::sync::{Arc, Mutex}; +use std::thread; + +fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Arc::clone(&counter); + handles.push(thread::spawn(move || { + // The guard drops at the end of this statement, ending the + // critical section as early as possible. + *counter.lock().unwrap() += 1; + })); + } + + for h in handles { + h.join().unwrap(); + } + + println!("counter: {}", *counter.lock().unwrap()); +} +``` + +`RwLock` splits the guard in two: `read` for shared access, `write` +for exclusive access. It pays off when reads dominate and are slow +enough to overlap usefully. + +```rust,editable +use std::sync::RwLock; + +fn main() { + let scores = RwLock::new(vec![1, 2, 3]); + + // Many readers can hold this guard at once. + println!("sum: {}", scores.read().unwrap().iter().sum::()); + + // Only one writer, and no readers while it is held. + scores.write().unwrap().push(4); + println!("after: {:?}", scores.read().unwrap()); +} +``` + +For a bare counter, an atomic skips the lock entirely. +`fetch_add` updates the value as one indivisible step. The `Ordering` +argument controls how the operation synchronizes with other threads; +`SeqCst` is the strictest and the right default unless profiling plus +expert review says otherwise (see the `std::sync::atomic` docs for the +full memory model). + +```rust,editable +use std::sync::atomic::{AtomicUsize, Ordering}; + +fn main() { + let hits = AtomicUsize::new(0); + + hits.fetch_add(1, Ordering::SeqCst); + hits.fetch_add(1, Ordering::SeqCst); + + println!("hits: {}", hits.load(Ordering::SeqCst)); +} +``` + +### See also: + +[`std::sync::Mutex`][mutex], [`std::sync::RwLock`][rwlock], +[`std::sync::atomic`][atomic], and [Threads][threads]. + +[mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[rwlock]: https://doc.rust-lang.org/std/sync/struct.RwLock.html +[atomic]: https://doc.rust-lang.org/std/sync/atomic/index.html +[threads]: threads.md + +### Exercise: Convert a counter to an atomic + +Task: Convert the `Arc>` counter below to `Arc`. + +
Hint + +Share the atomic through the threads and bump it with one operation that needs no guard or lock. + +
+ +
Solution + +```rust,editable +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread; + +fn main() { + let counter = Arc::new(AtomicUsize::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Arc::clone(&counter); + handles.push(thread::spawn(move || { + counter.fetch_add(1, Ordering::SeqCst); + })); + } + + for h in handles { + h.join().unwrap(); + } + + assert_eq!(counter.load(Ordering::SeqCst), 10); + println!("counter: {}", counter.load(Ordering::SeqCst)); +} +``` +
diff --git a/src/std_misc/threads.md b/src/std_misc/threads.md index 2f37d1c4a3..7c53d7cf6d 100644 --- a/src/std_misc/threads.md +++ b/src/std_misc/threads.md @@ -28,3 +28,96 @@ fn main() { ``` These threads will be scheduled by the OS. + +## Scoped threads + +`thread::spawn` requires its closure to own everything it captures +(`'static`), which forces awkward moves of stack data. `thread::scope` +(stable since 1.63) lifts that restriction: threads spawned inside the +scope may borrow stack data, because the scope joins every thread +before returning. + +```rust,editable +use std::thread; + +fn main() { + let numbers = vec![1, 2, 3, 4]; + + thread::scope(|s| { + // Borrow `numbers` instead of moving it: the scope guarantees + // both threads finish before `numbers` is used again below. + s.spawn(|| println!("len: {}", numbers.len())); + s.spawn(|| println!("sum: {}", numbers.iter().sum::())); + }); + + println!("still ours: {:?}", numbers); +} +``` + +## `Send` and `Sync` + +`Send` means a value may be moved to another thread; `Sync` means a +shared reference to it may be used from another thread. Most types are +both, and the compiler checks the bounds automatically when you spawn +or share. `Rc` is the classic exception: it is neither `Send` nor +`Sync`, which is why shared ownership across threads needs `Arc`. + +```rust,ignore +use std::rc::Rc; +use std::thread; + +fn main() { + let shared = Rc::new(42); + // Error: `Rc` cannot be sent between threads safely. + thread::spawn(move || println!("{}", shared)); +} +``` + +### Exercise: Fix a borrow across threads + +The program below does not compile. Press Run, read the compiler error, then fix it. + +Task: Make the spawned thread see `numbers` without moving it. + +```rust,editable,ignore,mdbook-runnable +// BROKEN: press Run to see the compiler error, then fix the program. +use std::thread; + +fn main() { + let numbers = vec![1, 2, 3, 4]; + + // Error: `thread::spawn` requires ownership, but `numbers` is borrowed + // and used again below. + let handle = thread::spawn(|| { + println!("sum: {}", numbers.iter().sum::()); + }); + handle.join().unwrap(); + + println!("still ours: {:?}", numbers); +} +``` + +
Hint + +Spawned threads may borrow stack data when a scope guarantees they finish first. + +
+ +
Solution + +```rust,editable +use std::thread; + +fn main() { + let numbers = vec![1, 2, 3, 4]; + + // The scope joins the thread before returning, so borrowing is safe. + thread::scope(|s| { + s.spawn(|| println!("sum: {}", numbers.iter().sum::())); + }); + + println!("still ours: {:?}", numbers); +} +``` + +
diff --git a/src/std_misc/threads/testcase_mapreduce.md b/src/std_misc/threads/testcase_mapreduce.md index c021b743d6..ac3c98cff6 100644 --- a/src/std_misc/threads/testcase_mapreduce.md +++ b/src/std_misc/threads/testcase_mapreduce.md @@ -124,6 +124,53 @@ What if the user decides to insert a lot of spaces? Do we _really_ want to spawn Modify the program so that the data is always chunked into a limited number of chunks, defined by a static constant at the beginning of the program. +### Exercise: Sum with scoped threads and an atomic + +Task: Rewrite the map-reduce above using `thread::scope` and one shared atomic sum. + +
Hint + +Scoped threads may borrow the data segments directly, so each thread can add its subtotal to a single shared counter. + +
+ +
Solution + +```rust,editable +use std::sync::atomic::{AtomicU32, Ordering}; +use std::thread; + +fn main() { + let data = "86967897737416471853297327050364959 +11861322575564723963297542624962850 +70856234701860851907960690014725639 +38397966707106094172783238747669219 +52380795257888236525459303330302837 +58495327135744041048897885734297812 +69920216438980873548808413720956532 +16278424637452589860345374828574668"; + + let total = AtomicU32::new(0); + + thread::scope(|s| { + for segment in data.split_whitespace() { + s.spawn(|| { + let subtotal: u32 = segment + .chars() + .map(|c| c.to_digit(10).expect("should be a digit")) + .sum(); + total.fetch_add(subtotal, Ordering::SeqCst); + }); + } + }); + + let total = total.load(Ordering::SeqCst); + println!("Final sum result: {}", total); + assert_eq!(total, 1342); +} +``` +
+ ### See also: * [Threads][thread] diff --git a/src/testing/property.md b/src/testing/property.md new file mode 100644 index 0000000000..61960cb7be --- /dev/null +++ b/src/testing/property.md @@ -0,0 +1,110 @@ +# Property testing + +Unit tests check examples; property tests check *invariants* across +hundreds of generated inputs. The [`proptest`][proptest] crate generates +random cases (and shrinks failures to minimal reproducers) for any type +implementing `Arbitrary` — integers, strings, vectors, and your own +types with a derived implementation. + +```rust,ignore +// Cargo.toml: proptest = "1" + +use proptest::prelude::*; + +// Reversing twice must return the original vector, for *every* vector. +proptest! { + #[test] + fn reverse_twice_is_identity(v: Vec) { + let mut twice = v.clone(); + twice.reverse(); + twice.reverse(); + prop_assert_eq!(twice, v); + } +} +``` + +```shell +$ cargo test reverse_twice_is_identity +# On failure, proptest prints the shrunk minimal input and saves a +# seed in proptest-regressions/ for a deterministic replay. +``` + +Reach for properties when examples feel arbitrary: round-trips +(serialize then parse), idempotence (formatting twice), model +agreement (your code vs. a naive version), and preservation (sort keeps +the length). One property often replaces a table of hand-picked cases. + +### See also: + +[Unit testing](unit_testing.md) and [`proptest`][proptest]. + +[proptest]: https://docs.rs/proptest/latest/proptest/ + +### Exercise: Test the list's string form + +Task: Write a property asserting that prepending an element grows the list's string form accordingly. + +
Hint + +The existing length method gives an independent oracle for how the representation should change. + +
+ +
Solution + +```rust,ignore +// Cargo.toml: proptest = "1" + +use proptest::prelude::*; + +use crate::List::*; + +enum List { + Cons(u32, Box), + Nil, +} + +impl List { + fn new() -> List { + Nil + } + + fn prepend(self, elem: u32) -> List { + Cons(elem, Box::new(self)) + } + + fn len(&self) -> u32 { + match *self { + Cons(_, ref tail) => 1 + tail.len(), + Nil => 0, + } + } + + fn stringify(&self) -> String { + match *self { + Cons(head, ref tail) => { + format!("{}, {}", head, tail.stringify()) + }, + Nil => { + format!("Nil") + }, + } + } +} + +proptest! { + #[test] + fn prepend_grows_stringify(elems: Vec, extra: u32) { + let mut list = List::new(); + for e in &elems { + list = list.prepend(*e); + } + let before = list.len() as usize; + list = list.prepend(extra); + // One more element means one more ", "-separated item before Nil. + prop_assert_eq!(list.len() as usize, before + 1); + prop_assert!(list.stringify().starts_with(&extra.to_string())); + } +} +``` +
diff --git a/src/trait/async_traits.md b/src/trait/async_traits.md new file mode 100644 index 0000000000..8c7d929dd0 --- /dev/null +++ b/src/trait/async_traits.md @@ -0,0 +1,108 @@ +# `async` in traits + +Since Rust 1.75, traits may declare `async fn` directly — no macro or +manual `Future` boxing needed. Each implementation's future captures +the receiver and arguments, exactly as if the method returned +`impl Future + '_`. + +```rust,editable +trait Notifier { + async fn notify(&self, msg: &str) -> String; +} + +struct Console; + +impl Notifier for Console { + async fn notify(&self, msg: &str) -> String { + format!("notified: {}", msg) + } +} + +fn main() { + // Calling `notify` only builds the future; running it needs a + // runtime (see below). This checks the trait shape compiles and + // that the future can be named and passed around. + fn returns_future(notifier: N) { + let _pending = notifier.notify("hi"); + } + returns_future(Console); + println!("trait with async fn compiles"); +} +``` + +Because the future borrows `&self`, runtimes that move futures across +threads need it to be `Send`. Add an explicit bound where that matters: +`async fn run(&self) -> T where Self: Sync`, or require the future +itself via `fn spawn(n: N)`. The compiler +tells you when the bound is missing. + +```rust,editable,ignore,mdbook-runnable +// Cargo.toml: tokio = { version = "1", features = ["full"] } + +trait Notifier { + async fn notify(&self, msg: &str) -> String; +} + +struct Console; + +impl Notifier for Console { + async fn notify(&self, msg: &str) -> String { + format!("notified: {}", msg) + } +} + +#[tokio::main] +async fn main() { + let console = Console; + println!("{}", console.notify("ferris").await); +} +``` + +### See also: + +[`async` and `.await`][async_await] and [Static and dynamic +dispatch][dispatch]. + +[async_await]: ../async/await_syntax.md +[dispatch]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html + +### Exercise: Add a defaulted trait method + +Task: Add a second method with a default body that calls the first. + +
Hint + +A default body runs like any other async block, so it can await calls on the same receiver. + +
+ +
Solution + +```rust,editable +trait Notifier { + async fn notify(&self, msg: &str) -> String; + + async fn notify_twice(&self, msg: &str) -> String { + let first = self.notify(msg).await; + let second = self.notify(msg).await; + format!("{}\n{}", first, second) + } +} + +struct Console; + +impl Notifier for Console { + async fn notify(&self, msg: &str) -> String { + format!("notified: {}", msg) + } +} + +fn main() { + fn returns_future(notifier: N) { + let _pending = notifier.notify_twice("hi"); + } + returns_future(Console); + println!("default method compiles"); +} +``` +
diff --git a/src/trait/iter.md b/src/trait/iter.md index 0e9efc20cd..5104969202 100644 --- a/src/trait/iter.md +++ b/src/trait/iter.md @@ -87,3 +87,45 @@ fn main() { [intoiter]: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html [iter]: https://doc.rust-lang.org/core/iter/trait.Iterator.html + +### Exercise: Predict skipped terms + +Task: Predict the four terms `fibonacci().skip(4).take(4)` yields, then verify by running the program. + +
Hint + +Count positions from zero through the sequence start, then take the next four after skipping. + +
+ +
Solution + +```rust,editable +struct Fibonacci { + curr: u32, + next: u32, +} + +impl Iterator for Fibonacci { + type Item = u32; + + fn next(&mut self) -> Option { + let current = self.curr; + self.curr = self.next; + self.next = current + self.next; + Some(current) + } +} + +fn main() { + // Positions 0..=7 are 0, 1, 1, 2, 3, 5, 8, 13. + let terms: Vec = Fibonacci { curr: 0, next: 1 } + .skip(4) + .take(4) + .collect(); + assert_eq!(terms, vec![3, 5, 8, 13]); + println!("skipped terms: {:?}", terms); +} +``` + +
diff --git a/src/unsafe/maybe_uninit.md b/src/unsafe/maybe_uninit.md new file mode 100644 index 0000000000..2b1aacbeb1 --- /dev/null +++ b/src/unsafe/maybe_uninit.md @@ -0,0 +1,99 @@ +# `MaybeUninit` + +Reading uninitialized memory is undefined behavior, even if you never +look at the value — the compiler may assume it cannot happen. When +initialization is genuinely two-phase (FFI out-pointers, buffering, +hand-rolled split borrows), `MaybeUninit` is the sanctioned +scratch space: write first, `assume_init` only after every byte is +initialized. + +```rust,editable +use std::mem::MaybeUninit; + +fn main() { + let mut slot = MaybeUninit::::uninit(); + + slot.write("ferris".to_string()); + + // Safe: `write` fully initialized the slot, so assuming it is + // initialized upholds the contract. Dropping `slot` itself without + // `assume_init` would leak; reading before `write` would be UB. + let value = unsafe { slot.assume_init() }; + println!("{}", value); +} +``` + +The canonical safe abstraction over raw parts is `split_at_mut`: two +mutable slices from one, proven disjoint by construction. + +```rust,editable +fn main() { + let mut data = [1, 2, 3, 4]; + let len = data.len(); + let ptr = data.as_mut_ptr(); + + // Safe: both halves lie inside `data` (which outlives them and has + // no other live references), and the ranges `[0, len/2)` and + // `[len/2, len)` do not overlap. + let (left, right) = unsafe { + ( + std::slice::from_raw_parts_mut(ptr, len / 2), + std::slice::from_raw_parts_mut(ptr.add(len / 2), len - len / 2), + ) + }; + + left[0] = 10; + println!("{:?} {:?}", left, right); +} +``` + +For foreign functions, combine `repr(C)` layouts with raw pointers at +the boundary, and wrap the result in a safe function immediately — see +[Foreign Function Interface][ffi] for the calling convention side. + +### See also: + +[`std::mem::MaybeUninit`][maybe], [`std::ptr`][ptr], and [Foreign +Function Interface][ffi]. + +[maybe]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html +[ptr]: https://doc.rust-lang.org/std/ptr/index.html +[ffi]: ../std_misc/ffi.md + +### Exercise: Write a safe split wrapper + +Task: Write a `split_at_mut`-style safe wrapper and state the two invariants that make it sound. + +
Hint + +The caller upholds one bound on the split point, and the two derived slices must never overlap. + +
+ +
Solution + +```rust,editable +fn split_at_mut(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { + // Invariant 1: the split point is in bounds, so both ranges are + // inside the allocation. Invariant 2: the ranges `[0, mid)` and + // `[mid, len)` are disjoint, so no two `&mut` alias. + assert!(mid <= slice.len()); + let ptr = slice.as_mut_ptr(); + let len = slice.len(); + unsafe { + ( + std::slice::from_raw_parts_mut(ptr, mid), + std::slice::from_raw_parts_mut(ptr.add(mid), len - mid), + ) + } +} + +fn main() { + let mut data = [1, 2, 3, 4, 5]; + let (left, right) = split_at_mut(&mut data, 2); + left[0] = 10; + right[0] = 30; + println!("{:?} | {:?}", left, right); +} +``` +