Files
learn-rust/reference/rust-syntax.html
T

721 lines
45 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>