Rust learning: lessons, notes, and exercise crates

This commit is contained in:
2026-09-06 23:59:08 +07:00
commit 6ddf41aa81
119 changed files with 10873 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Rust Book coverage map — what is owned, what is missing</title>
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body>
<h1>Coverage map</h1>
<p class="subtitle">Every chapter of The Rust Programming Language against this workspace · updated after lesson 0009</p>
<div class="callout">
The chapter list is taken verbatim from the book's
<a href="https://github.com/rust-lang/book/blob/main/src/SUMMARY.md">table of contents</a>, not from memory.
<strong>Read</strong> means you covered it in your two months. <strong>Produced</strong> means code you wrote in this
workspace uses it correctly — that is the only column that counts for the
<a href="../MISSION.md">mission</a>.
</div>
<h2>The map</h2>
<table>
<tr><th>Ch</th><th>Topic</th><th>State</th><th>Evidence / gap</th></tr>
<tr><td>1–2</td><td>Getting started, guessing game</td><td>Produced</td>
<td><code>hello_cargo/</code>, <code>guessing_game/</code></td></tr>
<tr><td>3</td><td>Variables, types, functions, control flow</td><td>Produced</td>
<td><code>variables/</code>, <code>function/</code>, <code>control_flow/</code>; 0001 diagnostic solid</td></tr>
<tr><td>4</td><td>Ownership, borrowing, slices</td><td>Produced</td>
<td><code>&amp;self</code> methods and <code>&amp;[Task]</code> accessor in <code>tasks/</code>. Watch for: you have never hit
a borrow-checker fight in your own multi-owner code, because nothing has needed two owners yet.</td></tr>
<tr><td>5</td><td>Structs and methods</td><td>Produced</td><td>0003 / 0004, <code>tasks/src/task.rs</code></td></tr>
<tr><td>6</td><td>Enums and <code>match</code></td><td>Produced</td>
<td><code>Status</code>, <code>Priority</code>, <code>Command</code>, and now <code>TaskError</code> (0006)</td></tr>
<tr><td>7</td><td>Packages, crates, modules</td><td>Produced</td>
<td><code>tasks/</code> is a lib + bin package with four modules</td></tr>
<tr><td>8</td><td>Collections: <code>Vec</code>, <code>String</code>, <code>HashMap</code></td><td>Produced</td>
<td>0008 drill, in <code>tasks/</code>: <code>HashMap&lt;Priority, usize&gt;</code> built with the <code>entry</code>/<code>or_insert</code>
counting idiom, <code>get().copied().unwrap_or(0)</code>, <code>Eq + Hash + Copy</code> derives on a key type, and a
<code>String</code> built by <code>collect</code>. Not done: <code>BTreeMap</code>, <code>HashSet</code>, string slicing by byte index.</td></tr>
<tr><td>9</td><td>Error handling: <code>panic!</code>, <code>Result</code>, <code>?</code></td><td>Produced</td>
<td>0005 + 0006: <code>?</code>, <code>ok_or</code>, own error enum, stderr + exit 1</td></tr>
<tr><td>10.1</td><td>Generic data types</td><td>Produced</td>
<td>0008 drill: <code>fn tally&lt;T, K, F&gt;(items: &amp;[T], key: F) -&gt; HashMap&lt;K, usize&gt;</code> in <code>tasks/src/stats.rs</code>,
with a <code>where</code> clause bounding <code>K: Eq + Hash</code> and <code>F: Fn(&amp;T) -&gt; K</code> — written from the
signature up, called at three different <code>T</code>/<code>K</code> pairs, one of them a non-<code>Copy</code> key.
Not done: generic <em>structs</em> and generic <code>impl</code> blocks.</td></tr>
<tr><td>10.2</td><td>Traits</td><td>Produced</td>
<td><code>Display for Task</code> (0005), <code>Display</code> + <code>Error</code> + <code>From</code> for <code>TaskError</code> (0006),
<code>FromStr for Task</code> with an associated <code>type Err</code> and a hand-written <code>PartialEq</code> (0007)</td></tr>
<tr><td>10.3</td><td>Lifetimes</td><td class="gap">Gap — untouched</td>
<td>Zero exposure in this workspace. You have dodged it by owning everything (<code>String</code>, not <code>&amp;'a str</code>).
Needed to read other people's code and to review a PR; not needed to ship your CLI.</td></tr>
<tr><td>11</td><td>Writing automated tests</td><td class="gap">Taught — drill pending</td>
<td>0009: <code>#[test]</code>, <code>#[cfg(test)] mod tests</code> with <code>use super::*</code>, the three assertion macros
and their failure output, <code>#[should_panic(expected)]</code> vs a <code>-&gt; Result&lt;(), E&gt;</code> test, unit vs
integration access (<code>E0603</code>/<code>E0616</code>), <code>tests/common/mod.rs</code>, the runner flags, and why
<code>src/main.rs</code> is untestable — <code>run</code> moves to the library and writes to <code>&amp;mut impl Write</code>.
Graded by six planted mutants, not by a test count.</td></tr>
<tr><td>12</td><td>I/O project: args, files, stderr, env</td><td>Produced</td>
<td>0007: <code>fs::read_to_string</code>/<code>fs::write</code>, <code>io::Error</code> + <code>ErrorKind::NotFound</code> match guard,
<code>env::var</code> with a default, on top of <code>env::args</code> and stderr + exit 1 from 0005. Not done: <code>BufReader</code>,
file locking, serde — none needed at this size.</td></tr>
<tr><td>13</td><td>Closures and iterators</td><td>Produced</td>
<td>0008 drill: <code>filter</code>/<code>map</code>/<code>collect</code> chains, <code>find</code> vs <code>position</code>,
<code>Vec::retain</code>, <code>lines()</code>, <code>collect::&lt;Result&lt;Vec&lt;_&gt;, E&gt;&gt;()</code> short-circuiting, and a
function that <em>takes</em> a closure (<code>F: Fn</code>). Not done: <code>fold</code>, <code>zip</code>,
<code>impl Iterator</code> for your own type.</td></tr>
<tr><td>14</td><td>Cargo, crates.io, workspaces</td><td>Skip for now</td>
<td>Learn <code>cargo add</code> and features when a dependency is actually needed (serde, axum).</td></tr>
<tr><td>15</td><td>Smart pointers: <code>Box</code>, <code>Rc</code>, <code>RefCell</code>, <code>Deref</code>, <code>Drop</code></td><td>Partial</td>
<td><code>Box&lt;dyn Error&gt;</code> met in 0005/0006. <code>Rc</code>/<code>RefCell</code> untouched — reach for them only when a real
shared-ownership problem appears, not before.</td></tr>
<tr><td>16</td><td>Concurrency: threads, channels, <code>Send</code>/<code>Sync</code></td><td class="gap">Gap</td>
<td>Untouched apart from the <code>Send + Sync</code> bound your 0006 test asserts. Prerequisite for understanding
why an axum handler must be <code>Send</code>.</td></tr>
<tr><td>17</td><td>Async: futures, <code>async</code>/<code>await</code>, streams</td><td class="gap">Gap — by design</td>
<td>Required for axum/tokio, deliberately last. The unused <code>trpl</code> dependency in <code>get-dependecies/</code> is
the abandoned first attempt. Do it after 8, 11, 13.</td></tr>
<tr><td>18</td><td>Trait objects, OOP patterns</td><td>Partial</td>
<td><code>dyn Error</code> is the same mechanism as 18.2. The state-machine pattern (18.3) is optional reading.</td></tr>
<tr><td>19</td><td>Patterns and matching</td><td>Partial</td>
<td><code>match</code> ✓, <code>if let</code> ✓ (0005 drill). Guards (<code>Some(x) if x &gt; 5</code>), <code>@</code> bindings,
<code>let ... else</code>, and <code>matches!</code> — one page of reading, high value per minute.</td></tr>
<tr><td>20</td><td>Unsafe, advanced traits, macros</td><td>Skip</td>
<td>Associated types and generic-parameter defaults matter eventually (serde uses them). Not now.</td></tr>
<tr><td>21</td><td>Final project: multithreaded web server</td><td>Not started</td>
<td>The natural capstone before axum — it is a backend service with no framework.</td></tr>
</table>
<h2>The order that follows from this</h2>
<p>After the 0009 drill, one gap in the book's core is left, and it is the mildest one:</p>
<ol>
<li><strong>Lifetimes</strong> (ch 10.3) — as reading practice, not as a build. You have written two without
noticing: <code>titles_with</code> returns <code>Vec&lt;&amp;str&gt;</code> borrowed from <code>&amp;self</code>, and
<code>Status::label</code> returns <code>&amp;str</code> the same way. Elision filled both annotations in. Reading
other people's signatures needs the explicit form.</li>
<li><strong>Patterns</strong> (ch 19) — one page: match guards, <code>@</code> bindings, <code>matches!</code>,
<code>let … else</code>. Your suite already uses <code>matches!</code>; the rest is high value per minute.</li>
</ol>
<p>Then serde → axum → async, where the traits from 0005–0008 and the testing from 0009 stop being an exercise
and start being the whole API surface: an axum handler is a function a test can call. Chapter 21's web server is
the natural capstone before a framework.</p>
<footer>
<p><strong>Primary source:</strong> <a href="https://doc.rust-lang.org/stable/book/">The Rust Programming Language</a> ·
chapter list from <a href="https://github.com/rust-lang/book/blob/main/src/SUMMARY.md">SUMMARY.md</a>.</p>
<p>Reference: <a href="rust-syntax.html">Rust syntax reference</a> ·
Lessons: <a href="../lessons/0006-your-own-error-type.html">0006</a> ·
<a href="../lessons/0007-files-and-fromstr.html">0007</a> ·
<a href="../lessons/0008-iterators-and-hashmap.html">0008</a> ·
<a href="../lessons/0009-writing-your-own-tests.html">0009</a></p>
<p>Disagree with a "Produced" or a "Gap"? Say so — this table decides what gets taught next, so it is worth arguing about.</p>
</footer>
</body>
</html>
+720
View File
@@ -0,0 +1,720 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Rust Syntax Reference — ch. 1–13</title>
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body>
<h1>Rust syntax reference</h1>
<p class="subtitle">The compressed essence of Rust Book ch. 1–13 · built for lookup while typing, not for reading</p>
<div class="callout">
Keep this open in a tab while you write code. Every line here is something you already met in ch1–13 — the point is to stop it blocking you mid-sentence.
</div>
<h2>Cargo commands</h2>
<pre><code>cargo new my_proj # create project (binary)
cargo new my_lib --lib # create library
cargo run # build + run
cargo run -- 95 83 # pass args to your program (after --)
cargo build # build only (debug)
cargo build --release # optimized build
cargo check # typecheck fast, no binary
cargo test # run #[test] functions
cargo add rand # add a dependency</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch01-03-hello-cargo.html">1.3 Hello, Cargo!</a></p>
<h2>Variables</h2>
<pre><code>let x = 5; // immutable
let mut y = 5; // mutable
y = 6; // ok, y is mut
const MAX: u32 = 100; // const: always typed, UPPER_CASE
let x = 5;
let x = x + 1; // shadowing: new variable, same name</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch03-01-variables-and-mutability.html">3.1</a></p>
<h2>Types</h2>
<pre><code>i8 i16 i32 i64 i128 isize // signed ints (i32 = default)
u8 u16 u32 u64 u128 usize // unsigned ints (usize = index/len type)
f32 f64 // floats (f64 = default)
bool // true / false
char // 'a' — 4 bytes, Unicode scalar
let t: (i32, f64) = (500, 6.4); // tuple
let (a, b) = t; // destructure
let first = t.0; // index
let arr: [u16; 10] = [0; 10]; // array: fixed length, 10 zeros
let arr = [1, 2, 3]; // inferred [i32; 3]
let s = "hi"; // &str — borrowed, fixed
let s = String::from("hi"); // String — owned, growable
let n: u32 = "42".parse().unwrap(); // str -> number
let n = "42".parse::&lt;u32&gt;().unwrap(); // same, turbofish form
let s = 42.to_string(); // number -> String
let len = arr.len() as u32; // numeric cast</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch03-02-data-types.html">3.2</a></p>
<h2>Functions</h2>
<pre><code>fn add(a: i32, b: i32) -> i32 {
a + b // no semicolon = this is the return value
}
fn shout(msg: &str) -> String {
msg.to_uppercase()
}
fn log(msg: &str) { // no -> means returns ()
println!("{msg}");
}</code></pre>
<p><strong>Statement vs expression:</strong> a line ending in <code>;</code> is a statement (produces no value). Drop the <code>;</code> and it is an expression whose value is returned. <code>return x;</code> works too, but is only idiomatic for early returns.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch03-03-how-functions-work.html">3.3</a></p>
<h2>Control flow</h2>
<pre><code>if n &lt; 5 { ... } else if n &lt; 10 { ... } else { ... }
let label = if n &gt; 0 { "pos" } else { "neg" }; // arms must be same type
loop { break; } // infinite until break
let got = loop { break 7; }; // loop can return a value
while n &lt; 10 { n += 1; }
for i in 0..5 { } // 0,1,2,3,4
for i in 0..=5 { } // 0..5 inclusive
for item in &vec { } // borrow each item
for (i, c) in word.char_indices() { }</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html">3.5</a></p>
<h2>Ownership — the three rules</h2>
<ol>
<li>Each value has exactly one <strong>owner</strong>.</li>
<li>There can be only one owner at a time.</li>
<li>When the owner goes out of scope, the value is <strong>dropped</strong>.</li>
</ol>
<pre><code>let s1 = String::from("hi");
let s2 = s1; // MOVE — s1 is now invalid
// println!("{s1}"); // compile error: borrow of moved value
let s2 = s1.clone(); // deep copy — both usable, costs allocation
let n1 = 5;
let n2 = n1; // COPY — i32 is Copy, n1 still valid</code></pre>
<p><strong><code>Copy</code></strong> types (stack-only, fixed size): all integers, <code>f32/f64</code>, <code>bool</code>, <code>char</code>, and tuples containing only <code>Copy</code> types. Everything heap-owning (<code>String</code>, <code>Vec</code>) moves instead.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html">4.1</a></p>
<h2>References &amp; borrowing</h2>
<pre><code>fn length(s: &String) -> usize { s.len() } // borrow, read-only
fn push_bang(s: &mut String) { s.push('!'); } // borrow, mutable
let mut s = String::from("hi");
length(&s); // pass an immutable reference
push_bang(&mut s); // pass a mutable reference</code></pre>
<p><strong>The borrowing rule:</strong> at any one time you may have <em>either</em> one mutable reference <em>or</em> any number of immutable references — never both. Enforced at compile time; this is what rules out data races without a GC.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html">4.2</a></p>
<h2>Slices</h2>
<pre><code>let s = String::from("hello world");
let hello = &s[0..5]; // &str — pointer + length, owns nothing
let world = &s[6..11];
let whole = &s[..];
let arr = [1, 2, 3, 4, 5];
let part: &[i32] = &arr[1..3]; // [2, 3]
fn total(nums: &[u32]) -&gt; u32 { ... } // takes Vec or array — prefer &amp;[T]</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch04-03-slices.html">4.3</a></p>
<h2>Structs</h2>
<pre><code>struct Rectangle { width: u32, height: u32 } // named fields
struct Point(i32, i32); // tuple struct
struct Marker; // unit struct
#[derive(Debug)] // enables {:?} printing
struct User { name: String, active: bool }
let r = Rectangle { width: 30, height: 50 };
println!("{r:?}"); // needs #[derive(Debug)]
impl Rectangle {
fn new(width: u32, height: u32) -> Self { // associated fn (no self)
Self { width, height } // field init shorthand
}
fn area(&self) -> u32 { // method: borrows
self.width * self.height
}
fn scale(&mut self, by: u32) { // method: borrows mutably
self.width *= by;
}
fn consume(self) -> u32 { self.width } // method: takes ownership
}
Rectangle::new(3, 4).area(); // :: for associated fn, . for method</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch05-03-method-syntax.html">5.1–5.3</a></p>
<h2>Enums &amp; pattern matching</h2>
<pre><code>enum Shape {
Circle(f64), // variant with data
Rect { w: f64, h: f64 }, // variant with named fields
Empty, // variant with no data
}
enum Option&lt;T&gt; { Some(T), None } // in std — "maybe a value"
enum Result&lt;T, E&gt; { Ok(T), Err(E) } // in std — "value or error"
match shape {
Shape::Circle(r) =&gt; 3.14 * r * r,
Shape::Rect { w, h } =&gt; w * h,
Shape::Empty =&gt; 0.0,
} // must be exhaustive
match score {
90..=100 =&gt; "A", // range pattern
n if n &gt; 50 =&gt; "pass", // match guard
_ =&gt; "F", // catch-all
}
if let Some(x) = maybe { ... } // one pattern, ignore rest
if let Some(x) = maybe { ... } else { ... }
while let Some(top) = stack.pop() { ... } // loop while pattern matches</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch06-02-match.html">6.1–6.3</a></p>
<h2>Modules &amp; paths</h2>
<pre><code>// src/lib.rs or src/main.rs — the crate root
mod front_of_house; // loads src/front_of_house.rs (or .../mod.rs)
mod hosting { // inline module
pub fn add_to_waitlist() {} // pub = visible outside this module
fn seat() {} // private (default)
}
crate::front_of_house::hosting::add_to_waitlist(); // absolute path
front_of_house::hosting::add_to_waitlist(); // relative path
super::hosting::add_to_waitlist(); // parent module
use crate::front_of_house::hosting; // bring into scope
use std::collections::HashMap;
use std::io::{self, Read}; // multiple items
use std::fmt::Result as FmtResult; // rename
pub use crate::hosting; // re-export</code></pre>
<p><strong>Everything is private by default.</strong> A child module can see its parent's items; a parent needs <code>pub</code> to see into a child. <code>pub</code> on a struct does <em>not</em> make its fields public — each field needs its own <code>pub</code>.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html">7.1–7.5</a></p>
<h3>Packages vs crates — the two-crate package</h3>
<pre><code>my_proj/
├── Cargo.toml # ONE package
├── src/
│ ├── lib.rs # crate 1: the LIBRARY, named after the package
│ ├── thing.rs # a module inside crate 1
│ └── main.rs # crate 2: the BINARY — a separate crate
└── tests/
└── spec.rs # crate 3: integration tests — separate again</code></pre>
<p>Cargo finds all three by filename. No <code>[lib]</code> or <code>[[bin]]</code> in <code>Cargo.toml</code> is needed.</p>
<pre><code>// src/thing.rs — INSIDE the library crate
use crate::other::Thing; // crate:: = root of the crate I am in
// src/main.rs — a DIFFERENT crate
use my_proj::thing::Thing; // must use the package name
// tests/spec.rs — also a different crate
use my_proj::thing::Thing; // same as main.rs</code></pre>
<p><code>use crate::..</code> in <code>main.rs</code> gives <code>error[E0432]: unresolved import</code>. When a path will not resolve, ask: <strong>which crate am I in right now?</strong></p>
<p>Three wiring states, in the order you hit them:</p>
<table>
<tr><th align="left">In <code>lib.rs</code></th><th align="left">Result</th></tr>
<tr><td>nothing</td><td><code>E0433: cannot find `thing`</code> — a file is not a module until declared</td></tr>
<tr><td><code>mod thing;</code></td><td><code>E0603: module `thing` is private</code></td></tr>
<tr><td><code>pub mod thing;</code></td><td>works</td></tr>
</table>
<p>Integration tests in <code>tests/</code> can only reach <code>pub</code> items via the library crate — they cannot see inside <code>main.rs</code> at all. That is the reason to put logic in <code>lib.rs</code> and keep <code>main.rs</code> thin.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch07-01-packages-and-crates.html">7.1 Packages and Crates</a> · <a href="https://doc.rust-lang.org/stable/book/ch11-03-test-organization.html">11.3 Test Organization</a></p>
<h3>Common derives</h3>
<pre><code>#[derive(Debug, Clone, Copy, PartialEq)]
enum Size { Small, Large }</code></pre>
<table>
<tr><th align="left">Derive</th><th align="left">Gives</th><th align="left">Needed for</th></tr>
<tr><td><code>Debug</code></td><td><code>{:?}</code></td><td>test failure output, logs</td></tr>
<tr><td><code>PartialEq</code></td><td><code>==</code></td><td><code>assert_eq!</code> on your type</td></tr>
<tr><td><code>Clone</code></td><td><code>.clone()</code></td><td>explicit second copy</td></tr>
<tr><td><code>Copy</code></td><td>assignment copies, not moves</td><td>small types, <strong>no heap data</strong></td></tr>
<tr><td><code>PartialOrd, Ord</code></td><td><code>&lt;</code>, <code>.sort()</code></td><td>ordering; enum variant order = the ordering</td></tr>
</table>
<p>A type containing a <code>String</code> or <code>Vec</code> <strong>cannot</strong> be <code>Copy</code>. Forget a derive and the compiler names the exact trait and type.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/appendix-03-derivable-traits.html">Appendix C — Derivable Traits</a></p>
<h2 id="collections">Collections</h2>
<pre><code>// Vec — owned, growable list
let mut v: Vec&lt;u32&gt; = Vec::new();
let v = vec![1, 2, 3];
v.push(4);
let third = &v[2]; // panics if out of range
let third = v.get(2); // returns Option&lt;&u32&gt; — safe
v.get(9).unwrap_or(&0); // default if missing
for n in &v { } // iterate borrowed
for n in &mut v { *n += 1; } // iterate mutably
v.len(); v.is_empty(); v.pop(); v.contains(&3);
// String — owned, growable, UTF-8
let mut s = String::new();
s.push_str("hi"); s.push('!');
let s = format!("{a}-{b}");
let joined = words.join(" ");
for w in text.split_whitespace() { }
for (i, c) in text.char_indices() { }
text.to_uppercase(); text.trim(); text.len(); // len = BYTES, not chars
// HashMap — key/value
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("a", 10);
m.get("a"); // Option&lt;&i32&gt;
let count = m.entry(key).or_insert(0); // insert-if-absent, returns &mut
*count += 1; // the counting idiom
for (k, v) in &m { }</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch08-01-vectors.html">8.1–8.3</a></p>
<h2 id="iterators">Iterators</h2>
<pre><code>// ONE trait, one method — everything else is a default method built on next()
pub trait Iterator {
type Item;
fn next(&mut self) -&gt; Option&lt;Self::Item&gt;;
}
// THREE ways in — the &self / &mut self / self choice, one element at a time
v.iter() // Item = &T reading; collection survives
v.iter_mut() // Item = &mut T editing in place; collection survives
v.into_iter() // Item = T taking the elements; collection consumed
// ADAPTERS — lazy, return an iterator, compose freely
.map(|x| ..) .filter(|x| ..) .enumerate() .skip(n) .take(n) .rev() .zip(other)
// CONSUMERS — do the work, return something that is not an iterator
.collect() .find(|x| ..) .position(|x| ..) .any(|x| ..) .all(|x| ..)
.count() .sum() .max() .min() .fold(init, |acc, x| ..) .for_each(|x| ..)
// find gives the ITEM, position gives the INDEX — both Option, both take ok_or
list.iter().find(|t| t.id == id).ok_or(NotFound(id))? // -&gt; &T
list.iter().position(|t| t.id == id).ok_or(NotFound(id))? // -&gt; usize
list.retain(|t| t.keep); // Vec method, not Iterator: delete in place, one pass
// COLLECT builds any FromIterator type — you must say which
let v: Vec&lt;String&gt; = it.collect();
let s: String = it.collect(); // String: FromIterator&lt;String&gt;
let m: HashMap&lt;K, V&gt; = pairs.collect(); // from an iterator of (K, V)
let r: Result&lt;Vec&lt;T&gt;, E&gt; = it.collect(); // SHORT-CIRCUITS on the first Err
let o: Option&lt;Vec&lt;T&gt;&gt; = it.collect(); // .. and on the first None
text.lines() // trailing "\n" is a TERMINATOR — no empty last item, strips \r
text.split('\n') // trailing "\n" is a SEPARATOR — yields a final ""</code></pre>
<p><strong>Errors you will see:</strong></p>
<pre><code>warning: unused `Map` that must be used
= note: iterators are lazy and do nothing unless consumed // no consumer
error[E0283]: type annotations needed
| let x = it.collect(); ------- type must be known at this point
= note: multiple `impl`s satisfying `_: FromIterator&lt;String&gt;` found
error[E0502]: cannot borrow `self.v` as immutable because it is also borrowed as mutable
// an iter_mut() result in a variable holds the borrow until its LAST use
error[E0507]: cannot move out of `x.field` which is behind a shared reference
// a closure over &T cannot give away an owned field — Copy, clone, or &str</code></pre>
<p><strong>Cost:</strong> an adapter chain is a tower of small structs compiled to one pass — no temporary
vectors, no runtime penalty against the equivalent <code>for</code> loop.</p>
<p><strong>When a loop still wins:</strong> folding many items into one accumulator you mutate. Iterators
replace loops that search, transform, or collect.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch13-02-iterators.html">13.2 Iterators</a> · std: <a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html">Iterator</a> · <a href="https://doc.rust-lang.org/std/iter/trait.FromIterator.html">FromIterator</a></p>
<h2 id="hashmap-keys">HashMap keys &amp; counting</h2>
<pre><code>use std::collections::HashMap;
// A key must be Eq + Hash: hash to pick the bucket, compare inside it.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] // Copy for fieldless enums
enum Priority { Low, Medium, High }
let mut m: HashMap&lt;Priority, usize&gt; = HashMap::new();
*m.entry(key).or_insert(0) += 1; // THE counting idiom: one lookup, &mut V back
m.entry(key).or_default(); // same, using Default::default()
m.get(&key).copied().unwrap_or(0); // Option&lt;&usize&gt; -&gt; usize, absent = 0
for (k, v) in &m { } // ARBITRARY order — impose your own before printing
HashMap::from([(Priority::High, 2)]); // literal, for tests</code></pre>
<p><code>Eq</code> is a marker: no methods, one extra promise over <code>PartialEq</code> — every value equals
itself. <code>f64</code> breaks it (<code>NAN != NAN</code>), so <code>f64</code> cannot be a key.</p>
<p>Want sorted keys instead of arbitrary order? <code>BTreeMap</code>, same API, <code>K: Ord</code>.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html">8.3 Hash maps</a> · std: <a href="https://doc.rust-lang.org/std/cmp/trait.Eq.html">Eq</a> · <a href="https://doc.rust-lang.org/std/hash/trait.Hash.html">Hash</a></p>
<h2>Error handling</h2>
<pre><code>panic!("boom"); // unrecoverable — stops the program
// Recoverable: return Result and let the caller decide
fn read_name() -&gt; Result&lt;String, io::Error&gt; {
let mut f = File::open("hello.txt")?; // ? = return Err early
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s) // must wrap success in Ok
}
match File::open("x.txt") {
Ok(f) =&gt; f,
Err(e) =&gt; match e.kind() {
ErrorKind::NotFound =&gt; File::create("x.txt").unwrap(),
_ =&gt; panic!("{e:?}"),
},
};
r.unwrap(); // value, or panic
r.expect("message"); // value, or panic with your message
r.unwrap_or(0); // value, or fallback
r.unwrap_or_else(|e| 0); // value, or compute fallback
r.is_ok(); r.is_err();
fn main() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; { ... } // main can return Result</code></pre>
<p><strong>Rule of thumb:</strong> <code>panic!</code> / <code>unwrap</code> in tests, prototypes, and truly impossible states. <code>Result</code> everywhere else — especially in library code and anything a backend service depends on.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch09-00-error-handling.html">9.1–9.3</a></p>
<h3>The Result pattern</h3>
<pre><code>enum Result&lt;T, E&gt; { Ok(T), Err(E) } // an enum: errors are VALUES
enum Option&lt;T&gt; { Some(T), None } // absence, with no reason attached</code></pre>
<p>Pick the shortest rung that fits:</p>
<pre><code>match r { Ok(v) =&gt; ..., Err(e) =&gt; ... } // full control, both arms required
if let Err(e) = r { ... } // only care about failure
r.unwrap_or(default) // fallback value
r.unwrap_or_else(|e| compute()) // fallback, computed
r.ok() // Result -&gt; Option (drops the reason)
r.map(|v| v + 1) // change Ok, leave Err untouched
r.is_ok() / r.is_err() // just ask
r.unwrap() / r.expect("why") // value, or PANIC
r? // hand the error to my caller</code></pre>
<p><strong>What <code>?</code> really does:</strong></p>
<pre><code>let v = thing()?;
// expands to roughly:
let v = match thing() {
Ok(value) =&gt; value,
Err(e) =&gt; return Err(From::from(e)), // early exit + TYPE CONVERSION
};</code></pre>
<p>The <code>From::from</code> step is why <code>?</code> can mix error types in one function:</p>
<pre><code>fn load_port(path: &str) -&gt; Result&lt;u16, Box&lt;dyn Error&gt;&gt; {
let text = fs::read_to_string(path)?; // io::Error -\
let port = text.trim().parse::&lt;u16&gt;()?; // ParseIntError -&gt; Box&lt;dyn Error&gt;
Ok(port)
}</code></pre>
<p><strong><code>?</code> requires the enclosing function to return <code>Result</code> or <code>Option</code>:</strong></p>
<pre><code>error[E0277]: the `?` operator can only be used in a function that returns `Result`
| cannot use the `?` operator in a function that returns `()`
help: consider adding return type</code></pre>
<p>Fix the signature, not the <code>?</code>. Even <code>main</code> can return <code>Result</code> — end it with <code>Ok(())</code>.</p>
<p><strong>Dropping a Result is a warning, not silence:</strong></p>
<pre><code>warning: unused `Result` that must be used
= note: this `Result` may be an `Err` variant, which should be handled
help: use `let _ = ...` to ignore the resulting value</code></pre>
<p><strong>panic vs propagate:</strong> <code>unwrap</code>/<code>expect</code> in tests, prototypes, and states that truly cannot happen. <code>Result</code> for anything touching files, args, network, or users. In a service: propagate with <code>?</code> through the inner layers, and decide what the user sees at <em>one</em> boundary (the request handler).</p>
<h3 id="error-types">Custom error types — the recipe</h3>
<pre><code>#[derive(Debug)] // Error requires Debug
enum SensorError {
Empty, // no data
NotANumber(ParseFloatError), // wrap the cause
OutOfRange(f64), // keep the bad value
}
impl fmt::Display for SensorError { // the human sentence
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SensorError::Empty =&gt; write!(f, "no reading given"),
SensorError::NotANumber(_) =&gt; write!(f, "reading is not a number"),
SensorError::OutOfRange(v) =&gt; write!(f, "{v} is outside -90..60"),
}
}
}
impl Error for SensorError {} // std::error::Error — marker, no body needed
// …or, when a variant WRAPS another error, hand the cause to source() instead:
impl Error for SensorError {
fn source(&self) -&gt; Option&lt;&amp;(dyn Error + 'static)&gt; {
match self {
SensorError::NotANumber(e) =&gt; Some(e), // the wrapped cause
_ =&gt; None, // nothing underneath
}
}
}
impl From&lt;ParseFloatError&gt; for SensorError { // makes bare `?` convert for you
fn from(e: ParseFloatError) -&gt; SensorError { SensorError::NotANumber(e) }
}</code></pre>
<p><strong>Three obligations, in order:</strong> <code>#[derive(Debug)]</code>, then <code>Display</code>, then the empty <code>impl Error</code>. Skip <code>Display</code> and the marker impl fails:</p>
<pre><code>error[E0277]: `SensorError` doesn't implement `std::fmt::Display`
13 | impl Error for SensorError {}
| ^^^^^^^^^^^ unsatisfied trait bound</code></pre>
<p><strong><code>From</code> is a trait with one method</strong> — <code>fn from(value: T) -&gt; Self</code>, i.e. "how to build me out of a T". <code>String::from("hi")</code> is the same trait. Three equivalent spellings once the impl exists:</p>
<pre><code>Err(SensorError::from(e)) // call it yourself
Err(e.into()) // same trait, from the value's side (Into comes free)
text.parse::&lt;f64&gt;()? // `?` calls From::from(e) for you
// what `?` expands to:
match thing() { Ok(v) =&gt; v, Err(e) =&gt; return Err(From::from(e)) }</code></pre>
<p><strong>Missing <code>From</code> impl, seen through <code>?</code>:</strong></p>
<pre><code>error[E0277]: `?` couldn't convert the error to `SensorError`
28 | Ok(text.parse::&lt;f64&gt;()?)
| --------------^ the trait `From&lt;ParseFloatError&gt;` is not implemented for `SensorError`
// with an annotated `let`, the same missing impl is reported as:
error[E0271]: type mismatch resolving `&lt;f64 as FromStr&gt;::Err == SensorError`
29 | let value: f64 = text.parse()?;
| ^^^^^ expected `SensorError`, found `ParseFloatError`</code></pre>
<p><code>ok_or("text")?</code> in a function returning <code>Result&lt;_, String&gt;</code> already relies on this: std ships <code>impl From&lt;&amp;str&gt; for String</code>.</p>
<p><strong>Reporting it in a CLI</strong> — <code>main -&gt; Result</code> prints with <code>{:?}</code> (Debug), so handle it yourself when a human reads the output:</p>
<pre><code>if let Err(e) = run(&args, &mut store) {
eprintln!("error: {e}"); // stderr, Display
process::exit(1); // status a script can test
}</code></pre>
<p><strong>Wrapped cause: <code>source()</code> or <code>Display</code>, never both.</strong> std's own rule — "the underlying error should be either returned by the outer error's <code>Error::source()</code>, or rendered by the outer error's <code>Display</code> implementation, but not both." So write your one sentence in <code>Display</code>, drop the <code>{e}</code>, and expose the cause through <code>source()</code> for logs and <code>--verbose</code>.</p>
<pre><code>let e = parse_reading("nope").unwrap_err();
e.to_string() // "reading is not a number" &lt;- yours
e.source().map(|s| s.to_string()) // Some("invalid float literal") &lt;- std's
</code></pre>
<p><strong>Reviewer's checklist for an error type</strong> (API Guidelines C-GOOD-ERR): implements <code>Error</code>; is <code>Send + Sync</code> (needed to cross threads, so needed by every web framework); never <code>()</code>; <code>Display</code> message lowercase, no trailing punctuation, concise. Assert the bounds in a test with an empty generic fn:</p>
<pre><code>fn assert_usable_as_error&lt;E: Error + Send + Sync + 'static&gt;() {}
assert_usable_as_error::&lt;TaskError&gt;(); // compile-time check, no body needed</code></pre>
<p><strong>Matching without naming every variant</strong> — <code>matches!</code> returns a bool:</p>
<pre><code>assert!(matches!(err, TaskError::BadId(_))); // true if the shape matches
if matches!(status, Status::Todo | Status::InProgress) { .. }</code></pre>
<p><strong>Adding a variant is a compiler-enforced review:</strong></p>
<pre><code>error[E0004]: non-exhaustive patterns: `&amp;TaskError::StoreFull` not covered
19 | match self {
| ^^^^ pattern `&amp;TaskError::StoreFull` not covered
note: `TaskError` defined here … 14 | StoreFull, --------- not covered</code></pre>
<p>That failure is the reason to prefer an enum over a <code>String</code> — and the reason not to write <code>_ =&gt; ..</code> when matching your own error type at the reporting edge.</p>
<p><strong>Two more errors from the same migration:</strong></p>
<pre><code>error[E0432]: unresolved import `crate::error` // `pub mod error;` missing
error[E0369]: binary operation `==` cannot be applied to type `TaskError`
note: `TaskError` does not implement `PartialEq` // tests use assert_eq!</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch09-02-recoverable-errors-with-result.html">9.2</a> · std: <a href="https://doc.rust-lang.org/std/error/trait.Error.html">Error</a> · <a href="https://doc.rust-lang.org/std/convert/trait.From.html">From</a></p>
<h2 id="files">Files, <code>io::Error</code> &amp; <code>FromStr</code></h2>
<pre><code>use std::fs;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
fs::read_to_string(path)? // io::Result&lt;String&gt; = Result&lt;String, io::Error&gt;
fs::write(path, text)? // io::Result&lt;()&gt; — creates or truncates, one call
text.lines() // iterator of &amp;str, no trailing newlines
// a path from the environment, with a default
let path = PathBuf::from(
env::var("TASKS_FILE").unwrap_or_else(|_| "tasks.txt".to_string()));</code></pre>
<p><strong>One io failure is not another</strong> — <code>e.kind()</code> returns an <code>ErrorKind</code>. A missing file on first run is expected; everything else is not. Use a <em>match guard</em>:</p>
<pre><code>let text = match fs::read_to_string(path) {
Ok(text) =&gt; text,
Err(e) if e.kind() == ErrorKind::NotFound =&gt; return Ok(Store::new()),
Err(e) =&gt; return Err(TaskError::Io(e)), // permissions, disk, …
};</code></pre>
<p><strong><code>FromStr</code> is the trait behind <code>.parse()</code></strong>. Implement it and <code>.parse::&lt;YourType&gt;()</code> starts working — <code>type Err</code> is an <em>associated type</em>: a slot the implementor fills once, not a parameter the caller passes.</p>
<pre><code>pub trait FromStr: Sized {
type Err;
fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt;;
}
impl FromStr for Task {
type Err = TaskError;
fn from_str(line: &amp;str) -&gt; Result&lt;Task, TaskError&gt; {
let bad = || TaskError::BadLine(line.to_string()); // built on failure
let mut parts = line.splitn(4, '|'); // splitn: last piece keeps its '|'
let id: u32 = parts.next().ok_or_else(bad)?.parse().map_err(|_| bad())?;
..
}
}</code></pre>
<pre><code>error[E0046]: not all trait items implemented, missing: `Err` // no `type Err`
error[E0277]: the trait bound `Task: FromStr` is not satisfied // no impl</code></pre>
<p><strong><code>?</code> vs <code>map_err</code>:</strong> only <em>one</em> <code>impl From&lt;T&gt; for YourError</code> may exist per <code>T</code> (a second is <code>error[E0119]: conflicting implementations</code>). So <code>?</code> handles the canonical meaning, and <code>map_err</code> names the variant for every other meaning of the same error type.</p>
<pre><code>let id: u32 = text.parse()?; // -&gt; BadId, via From
let id: u32 = text.parse().map_err(|_| bad())?; // -&gt; BadLine, chosen here</code></pre>
<p><strong>Not everything derives.</strong> <code>io::Error</code> is not <code>PartialEq</code>, so a variant holding one breaks <code>#[derive(PartialEq)]</code> (<code>E0369</code>). Write it by hand; <code>mem::discriminant</code> covers the payload-free variants:</p>
<pre><code>impl PartialEq for TaskError {
fn eq(&amp;self, other: &amp;Self) -&gt; bool {
match (self, other) { // match on a TUPLE of both
(Io(a), Io(b)) =&gt; a.kind() == b.kind(),
(NotFound(a), NotFound(b)) =&gt; a == b,
_ =&gt; std::mem::discriminant(self) == std::mem::discriminant(other),
}
}
}</code></pre>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch12-02-reading-a-file.html">12.2</a>, <a href="https://doc.rust-lang.org/stable/book/ch12-05-working-with-environment-variables.html">12.5</a> · std: <a href="https://doc.rust-lang.org/std/fs/">fs</a> · <a href="https://doc.rust-lang.org/std/io/enum.ErrorKind.html">ErrorKind</a> · <a href="https://doc.rust-lang.org/std/str/trait.FromStr.html">FromStr</a></p>
<h2 id="traits">Traits &amp; generics</h2>
<pre><code>trait Reading {
fn celsius(&self) -&gt; f64; // REQUIRED — semicolon
fn label(&self) -&gt; String { // DEFAULT — has a body, may be overridden
format!("{:.1}C", self.celsius())
}
}
impl Reading for Kettle { // impl TRAIT for TYPE
fn celsius(&self) -&gt; f64 { (self.fahrenheit - 32.0) * 5.0 / 9.0 }
}
impl fmt::Display for Thermometer { // the trait behind `{}`
fn fmt(&self, f: &mut fmt::Formatter) -&gt; fmt::Result {
write!(f, "{} [{:.1}C]", self.room, self.celsius) // INTO f, no `;`
}
}</code></pre>
<p><strong>Bounds — four spellings, one meaning</strong> ("any T that implements Reading"):</p>
<pre><code>fn show&lt;T: Reading&gt;(r: &T) // angle brackets
fn show(r: &impl Reading) // shorthand
fn show&lt;T&gt;(r: &T) where T: Reading // where clause, for long lists
fn show(r: &dyn Reading) // trait OBJECT: type chosen at runtime
fn show&lt;T: Reading + Clone&gt;(r: &T) // two promises at once</code></pre>
<p><code>Box&lt;dyn Error&gt;</code> is the same <code>dyn</code> idea: "some heap value that implements <code>Error</code>". Use it when the failures are unrelated and an enum is not worth writing.</p>
<p><strong>A generic function of your own</strong> — one definition, one specialised copy compiled per set of
types actually used (monomorphisation), so the generality is free at runtime:</p>
<pre><code>pub fn tally&lt;T, K, F&gt;(items: &[T], key: F) -&gt; HashMap&lt;K, usize&gt;
where
K: Eq + Hash, // required by the HashMap being returned, not by the body
F: Fn(&T) -&gt; K, // each closure has its OWN anonymous type
{
let mut counts = HashMap::new();
for item in items { *counts.entry(key(item)).or_insert(0) += 1; }
counts
}
tally(&tasks, |t| t.priority) // T = Task, K = Priority
tally(&["a", "bb"], |w| w.len()) // T = &str, K = usize</code></pre>
<p>The clause is a contract both ways: callers must satisfy it, and the body may use <em>only</em> what it
promises. <code>Fn</code> = borrows its captures · <code>FnMut</code> = mutates them · <code>FnOnce</code> =
consumes them. Take <code>Fn</code> unless you need more.</p>
<p><strong>Errors you will see:</strong></p>
<pre><code>error[E0046]: not all trait items implemented, missing: `celsius`
| ^^^^^^^^^^^^^^^^^^^^^^^ missing `celsius` in implementation
error[E0277]: `Kettle` doesn't implement `std::fmt::Display`
= note: in format strings you may be able to use `{:?}` instead
error[E0599]: no method named `celsius` found for reference `&&T` in the current scope
= help: items from traits can only be used if the trait is implemented and in scope
note: `Reading` defines an item `celsius`, perhaps you need to implement it</code></pre>
<p>That last one is the missing-bound error: a bare <code>&lt;T&gt;</code> promises nothing, so no method exists on it. Add the bound.</p>
<p><strong>Free consequences of one impl:</strong> <code>Display</code> grants <code>ToString</code> (so <code>.to_string()</code> works); <code>From&lt;A&gt; for B</code> grants <code>A.into(): B</code> and makes <code>?</code> convert.</p>
<p><strong>Orphan rule:</strong> <code>impl Trait for Type</code> is allowed only if the trait or the type is yours. <code>Display for MyTask</code> ✓ · <code>MyTrait for Vec&lt;T&gt;</code> ✓ · <code>Display for Vec&lt;String&gt;</code> ✗.</p>
<p><strong>Trait in scope:</strong> to call a trait method you must have the trait imported (<code>use std::io::Write;</code> before <code>.write_all()</code>). <code>println!("{}")</code> is exempt — the macro names <code>Display</code> by full path.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html">10.2 Traits</a> · <a href="https://doc.rust-lang.org/stable/book/ch10-01-syntax.html">10.1 Generics</a> · std: <a href="https://doc.rust-lang.org/std/fmt/trait.Display.html">Display</a></p>
<h2>Printing</h2>
<pre><code>println!("plain text");
println!("{}", value); // Display
println!("{value}"); // inline variable (preferred)
println!("{value:?}"); // Debug — needs #[derive(Debug)]
println!("{value:#?}"); // Debug, pretty-printed
eprintln!("to stderr");</code></pre>
<h3>Debug vs Display</h3>
<p>Two independent traits. A type can have one, both, or neither.</p>
<pre><code>{} {value} needs Display — text for END USERS
{:?} {value:?} needs Debug — text for PROGRAMMERS
{:#?} {value:#?} needs Debug — same, multi-line</code></pre>
<pre><code>let s = String::from("hi\tthere");
println!("{s}"); // hi there <- raw, for a user
println!("{s:?}"); // "hi\tthere" <- quoted + escaped, exact</code></pre>
<pre><code>#[derive(Debug)] // compiler writes Debug for you
struct Config { name: String }
impl fmt::Display for Point { // Display is NEVER derivable — write by hand
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}</code></pre>
<p><strong>Why no <code>derive(Display)</code>:</strong> how a value should look to a human is your program's decision, not something the compiler can guess. <code>Debug</code> has one obvious form (type name + fields), so it can be generated.</p>
<p><strong>Why <code>Vec</code> has <code>Debug</code> but no <code>Display</code>:</strong> there is no single correct way to show a list to a user (commas? bullets? brackets?), so std refuses to pick. You choose:</p>
<pre><code>println!("{v:?}"); // ["a", "b"] — diagnostics
println!("{}", v.join(", ")); // a, b — you pick the format</code></pre>
<p><strong>Errors you will see:</strong></p>
<pre><code>error[E0277]: `Vec&lt;String&gt;` doesn't implement `std::fmt::Display`
= note: in format strings you may be able to use `{:?}` instead
error[E0277]: `Point` doesn't implement `Debug`
= note: add `#[derive(Debug)]` to `Point` or manually `impl Debug for Point`</code></pre>
<p><strong>Rule of thumb:</strong> use <code>{:?}</code> while developing and in logs. Write a <code>Display</code> impl only when a real person reads the output. Custom error types want both — <code>Display</code> for the caller's message, <code>Debug</code> for the log.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch05-02-example-structs.html">5.2 (derive Debug)</a> · <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html">10.2 Traits</a> · <a href="https://doc.rust-lang.org/std/fmt/">std::fmt docs</a></p>
<h2 id="tests">Tests</h2>
<pre><code>#[cfg(test)] // compiled by `cargo test`, not by `cargo build`
mod tests {
use super::*; // the child module needs its parent's items
#[test] // no arguments, no return value
fn a_task_starts_as_todo() {
assert_eq!(Task::new(1, "x", Low).status, Status::Todo);
}
}</code></pre>
<p><strong>A test fails by panicking.</strong> Each test runs on its own thread; the harness marks it failed when that thread dies. So every assertion macro is a wrapper that panics, and <code>unwrap</code>/<code>expect</code> in a test body is a legitimate assertion.</p>
<pre><code>assert!(cond) // prints the SOURCE TEXT of cond
assert!(cond, "id {} was wrong", id) // extra args go to format!
assert_eq!(a, b) // prints both values as `left` / `right`
assert_ne!(a, b) // same, passes when they differ</code></pre>
<p><code>assert_eq!</code> needs <code>PartialEq</code> (to compare) and <code>Debug</code> (to print) on the values — that is what the <code>#[derive(Debug, PartialEq)]</code> on your own types is for. Prefer it over <code>assert!(a == b)</code>, which throws the values away. An assertion inside a loop needs a message naming the case.</p>
<p><strong>Two ways to test a failure</strong> — one per way your code fails:</p>
<pre><code>#[test]
#[should_panic(expected = "at most 30")] // substring of the panic message
fn refuses_a_high_target() { Thermostat::new(99); }
#[test]
fn a_reload_sees_what_was_saved() -&gt; Result&lt;(), TaskError&gt; {
store.save(&amp;path)?; // `?` for SETUP that must work
assert!(Store::load(&amp;bad).is_err()); // is_err for the tested failure
Ok(())
}</code></pre>
<p>Always give <code>should_panic</code> its <code>expected</code>, or any panic passes the test. <code>#[should_panic]</code> and a <code>Result</code> return cannot be combined; assert with <code>is_err()</code> or <code>unwrap_err()</code> instead. A <code>Result</code> test's error type only needs <code>Debug</code>.</p>
<p><strong>Two homes, two levels of access:</strong></p>
<table>
<tr><th></th><th>Unit test</th><th>Integration test</th></tr>
<tr><td>Lives in</td><td>bottom of the source file</td><td><code>tests/&lt;name&gt;.rs</code></td></tr>
<tr><td>Compiled as</td><td>part of your crate</td><td>its own separate crate</td></tr>
<tr><td>Can reach</td><td>private items too</td><td>the public API only</td></tr>
<tr><td>Needs</td><td><code>#[cfg(test)]</code> + <code>use super::*</code></td><td><code>use mycrate::…</code></td></tr>
<tr><td>Run alone</td><td><code>cargo test --lib</code></td><td><code>cargo test --test name</code></td></tr>
</table>
<p><strong>Nothing in <code>src/main.rs</code> is testable</strong> — a binary crate exposes nothing to <code>use</code>. Keep <code>main</code> thin, put the logic in the library, and take the output destination as a parameter so a test can read it:</p>
<pre><code>pub fn run(args: &amp;[String], store: &amp;mut Store, out: &amp;mut impl Write)
-&gt; Result&lt;(), TaskError&gt;
{
writeln!(out, "added task {}", id)?; // never println! in library code
}
run(&amp;args, &amp;mut store, &amp;mut io::stdout().lock())?; // in main
let mut out: Vec&lt;u8&gt; = Vec::new(); // in a test
run(&amp;args, &amp;mut store, &amp;mut out)?;
assert_eq!(String::from_utf8(out)?, "added task 1\n");</code></pre>
<p><strong>Helpers shared by two integration files</strong> go in <code>tests/common/mod.rs</code>, never <code>tests/common.rs</code> — a plain file there is compiled as its own test crate and shows up as a stray <code>running 0 tests</code> section.</p>
<pre><code>tests/
├── common/mod.rs mod common; then common::three_tasks()
├── cli.rs #![allow(dead_code)] in mod.rs: each crate uses some
└── mine.rs</code></pre>
<p><strong>Tests run in parallel</strong> and share nothing safely — no shared file, no current directory, no environment variable. Give every file-touching test its own path (process id + an <code>AtomicU32</code> counter, inside <code>env::temp_dir()</code>).</p>
<pre><code>cargo test # everything
cargo test warmer # every test whose FULL name contains it
cargo test --lib # unit tests only (--test cli = one file)
cargo test -- --show-output # also print stdout of tests that PASSED
cargo test -- --ignored # only the #[ignore] ones
cargo test -- --test-threads=1 # no parallelism — diagnose, do not fix</code></pre>
<p>Flags before <code>--</code> go to cargo, flags after it go to the test binary. The module path is part of a test's name (<code>task::tests::…</code>), so filtering on a module name runs that module.</p>
<pre><code>running 4 tests
test tests::refuses_a_high_target - should panic ... ok
test tests::slow_one ... ignored, only when the range changes
test result: ok. 3 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
// measured = nightly benchmarks · filtered out = excluded by your filter</code></pre>
<p><strong>Errors you will see:</strong></p>
<pre><code>error[E0433]: cannot find type `Thermostat` in this scope // no use super::*
error[E0603]: function `capped` is private // private fn, from tests/
error[E0616]: field `target` of struct `Thermostat` is private
help: a method `target` also exists, call it with parentheses</code></pre>
<p><strong>Judge a suite by the bugs it kills, not by the number of tests.</strong> Change one operator in the code by hand, run the suite, and put it back: if nothing went red, that behaviour is untested.</p>
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html">11.1 Writing tests</a> · <a href="https://doc.rust-lang.org/stable/book/ch11-02-running-tests.html">11.2 Running them</a> · <a href="https://doc.rust-lang.org/stable/book/ch11-03-test-organization.html">11.3 Organization</a></p>
<footer>
<p><strong>Primary source:</strong> <a href="https://doc.rust-lang.org/stable/book/">The Rust Programming Language</a>, chapters 1–13.</p>
<p>Lessons: <a href="../lessons/0001-diagnostic-ch1-9.html">0001 Diagnostic</a> · <a href="../lessons/0002-write-a-cli-from-blank.html">0002 Write a CLI from blank</a> · <a href="../lessons/0004-structs-enums-packages.html">0004 Structs, enums, packages</a> · <a href="../lessons/0003-build-a-task-cli.html">0003 Build a task CLI</a> · <a href="../lessons/0005-traits-display-and-errors.html">0005 Traits, Display, errors</a> · <a href="../lessons/0006-your-own-error-type.html">0006 Your own error type</a> · <a href="../lessons/0007-files-and-fromstr.html">0007 Files, io::Error, FromStr</a> · <a href="../lessons/0008-iterators-and-hashmap.html">0008 Iterators, HashMap, generics</a> · <a href="../lessons/0009-writing-your-own-tests.html">0009 Writing your own tests</a></p>
<p>Other reference: <a href="book-coverage.html">Coverage map</a> — every book chapter marked Read / Produced / Gap, and what gets taught next.</p>
<p>Something here unclear or contradicting what you remember? Ask the agent.</p>
</footer>
</body>
</html>