543 lines
33 KiB
HTML
543 lines
33 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Traits — the promise, and the two your CLI needs</title>
|
|
<link rel="stylesheet" href="../assets/style.css" />
|
|
</head>
|
|
<body>
|
|
<h1>Traits, <code>Display</code>, and a real error type</h1>
|
|
<p class="subtitle">Lesson 0005 · after <a href="0003-build-a-task-cli.html">0003</a> · reading, then a 20-minute drill on your own code · ~30 minutes</p>
|
|
|
|
<p>Every code block, every output line, and every error message on this page was produced by running it. Nothing here is written from memory.</p>
|
|
|
|
<p>The demo domain is a temperature sensor, deliberately — your project is a task CLI, so nothing below can be pasted into it. You have to translate, and translating is where the learning happens.</p>
|
|
|
|
<h2>First: what 0003 actually showed</h2>
|
|
|
|
<p>You passed all 17 tests. The three modules — <code>task</code>, <code>command</code>, <code>store</code> — do what the spec asked, you used <code>?</code> with <code>ok_or</code>, and <code>find(|task| task.id == id)</code> is the idiomatic answer, not a loop. Structs, enums and the two-crate package are no longer the weak spot.</p>
|
|
|
|
<p>But the tests only reach the library. <code>main.rs</code> is the one file no test could see, and it is the one file that misses the contract. Run your own crate:</p>
|
|
|
|
<pre><code>$ cargo run -- fly
|
|
no valid commands
|
|
$ echo $?
|
|
0
|
|
|
|
$ cargo run -- done 9
|
|
thread 'main' (146760) panicked at src/main.rs:25:33:
|
|
called `Result::unwrap()` on an `Err` value: "id not found"
|
|
$ echo $?
|
|
101</code></pre>
|
|
|
|
<p>The spec said: <em>errors print to stderr and exit with status 1</em>. What happens instead is three separate faults:</p>
|
|
|
|
<ol>
|
|
<li><code>println!</code> sends errors to <strong>stdout</strong>, so a pipe or a redirect mixes them into real output.</li>
|
|
<li>Exit status <strong>0</strong> means "success", so any script calling your CLI believes the failure worked.</li>
|
|
<li><code>.unwrap()</code> on <code>complete()</code> and <code>remove()</code> <strong>panics</strong> — status 101 and a backtrace hint — on the ordinary, expected case of a wrong id.</li>
|
|
</ol>
|
|
|
|
<p>That is not a syntax gap. It is the exact thing your mission names: <em>errors with <code>Result</code>, not <code>panic!</code></em>. And the tidy fix needs one thing you have not met yet: traits. So — traits first, then you fix those three faults yourself in the drill at the bottom.</p>
|
|
|
|
<h2>Part 1 — A trait is a promise</h2>
|
|
|
|
<p>A trait is a <strong>list of method signatures that a type can promise to provide</strong>. Nothing more. If you have used an interface, you have the shape already; the differences come later.</p>
|
|
|
|
<pre><code>trait Reading {
|
|
fn celsius(&self) -> f64;
|
|
|
|
fn label(&self) -> String {
|
|
format!("{:.1}C", self.celsius())
|
|
}
|
|
}</code></pre>
|
|
|
|
<p>Two kinds of method are in there, and the difference is the whole of Part 1:</p>
|
|
|
|
<ul>
|
|
<li><code>celsius</code> ends in a <strong>semicolon</strong> — a required method. Every implementor must write it.</li>
|
|
<li><code>label</code> has a <strong>body</strong> — a default method. Implementors get it for free and may override it. Note that the default calls <code>self.celsius()</code>, a method the trait does not yet have an implementation for. That is allowed: the trait can build on its own promises.</li>
|
|
</ul>
|
|
|
|
<p>Now two unrelated types keep that promise. <code>impl</code> <em>Trait</em> <code>for</code> <em>Type</em>:</p>
|
|
|
|
<pre><code>struct Thermometer { room: String, celsius: f64 }
|
|
struct Kettle { fahrenheit: f64 }
|
|
|
|
impl Reading for Thermometer {
|
|
fn celsius(&self) -> f64 { self.celsius }
|
|
|
|
fn label(&self) -> String { // overrides the default
|
|
format!("{} is {:.1}C", self.room, self.celsius)
|
|
}
|
|
}
|
|
|
|
impl Reading for Kettle {
|
|
fn celsius(&self) -> f64 { // takes the default `label`
|
|
(self.fahrenheit - 32.0) * 5.0 / 9.0
|
|
}
|
|
}</code></pre>
|
|
|
|
<pre><code>1. kitchen is 21.5C
|
|
2. 100.0C</code></pre>
|
|
|
|
<p>Line 1 is the override, line 2 is the default method doing the work for a type whose numbers were never even in Celsius. Two types, one vocabulary.</p>
|
|
|
|
<div class="callout">
|
|
<strong>The mental model.</strong> An <code>impl Type</code> block is what a type can do <em>for itself</em>. An <code>impl Trait for Type</code> block is a type <em>keeping a promise someone else defined</em>. Same keyword, two different jobs — that is why 0004's <code>impl Task { .. }</code> and this page's <code>impl Reading for Kettle { .. }</code> look so similar.
|
|
</div>
|
|
|
|
<p>Break the promise — drop <code>celsius</code> from the <code>Kettle</code> impl — and the compiler names exactly what is missing:</p>
|
|
|
|
<pre><code>error[E0046]: not all trait items implemented, missing: `celsius`
|
|
--> src/main.rs:31:1
|
|
|
|
|
5 | fn celsius(&self) -> f64;
|
|
| ------------------------- `celsius` from trait
|
|
...
|
|
31 | impl Reading for Kettle {
|
|
| ^^^^^^^^^^^^^^^^^^^^^^^ missing `celsius` in implementation</code></pre>
|
|
|
|
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html#defining-a-trait">10.2 Defining a Trait</a> · <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html#default-implementations">Default Implementations</a></p>
|
|
|
|
<h2>Part 2 — <code>Display</code>: the trait behind <code>{}</code></h2>
|
|
|
|
<p>Here is the part that pays off immediately. <code>println!("{}", x)</code> is not magic and it is not built into the language for "printable things". It calls <strong>one trait method</strong>, <code>Display::fmt</code>. A type prints with <code>{}</code> if — and only if — someone implemented that trait for it.</p>
|
|
|
|
<pre><code>use std::fmt;
|
|
|
|
impl fmt::Display for Thermometer {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} [{:.1}C]", self.room, self.celsius)
|
|
}
|
|
}</code></pre>
|
|
|
|
<p>Three things in that signature to notice, then never think about again:</p>
|
|
|
|
<ul>
|
|
<li>You do not return a string. You <strong>write into</strong> the formatter <code>f</code> that the caller supplied — no allocation happens for a <code>println!</code>.</li>
|
|
<li><code>write!</code> is <code>format!</code> aimed at a destination. It has the same syntax and returns the <code>fmt::Result</code> you need, which is why the body is one line with no semicolon.</li>
|
|
<li><code>fmt::Result</code> is just <code>Result<(), fmt::Error></code> under a different name.</li>
|
|
</ul>
|
|
|
|
<pre><code>3. kitchen [21.5C]
|
|
4. to_string gave: kitchen [21.5C]</code></pre>
|
|
|
|
<p>Line 4 is the bonus and it is worth understanding: <code>.to_string()</code> was never written for <code>Thermometer</code>. The standard library says <em>every</em> type implementing <code>Display</code> gets <code>ToString</code> automatically. One trait implemented, a second one granted. <span class="cite">(<a href="https://doc.rust-lang.org/std/string/trait.ToString.html">std: <code>ToString</code></a> — "implemented automatically for any type which implements <code>Display</code>")</span></p>
|
|
|
|
<p>And the type <em>without</em> the impl — <code>Kettle</code> — cannot use <code>{}</code> at all:</p>
|
|
|
|
<pre><code>error[E0277]: `Kettle` doesn't implement `std::fmt::Display`
|
|
--> src/main.rs:63:23
|
|
|
|
|
63 | println!("5. {}", k);
|
|
| -- ^ `Kettle` cannot be formatted with the default formatter
|
|
|
|
|
help: the trait `std::fmt::Display` is not implemented for `Kettle`
|
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead</code></pre>
|
|
|
|
<p>You have met this error already, in 0003 — that is what <code>#[derive(Debug)]</code> and <code>{:?}</code> were for. Now the split is clear:</p>
|
|
|
|
<table>
|
|
<tr><th> </th><th><code>Debug</code> — <code>{:?}</code></th><th><code>Display</code> — <code>{}</code></th></tr>
|
|
<tr><td>Audience</td><td>You, debugging</td><td>The user of the program</td></tr>
|
|
<tr><td>How you get it</td><td><code>#[derive(Debug)]</code></td><td>Hand-written; no derive exists</td></tr>
|
|
<tr><td>Shape</td><td>Structure: <code>Task { id: 1, .. }</code></td><td>Whatever you decide it reads like</td></tr>
|
|
</table>
|
|
|
|
<p>There is no <code>#[derive(Display)]</code> on purpose: the compiler can print your fields mechanically, but only you know how the sentence should read.</p>
|
|
|
|
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html#implementing-a-trait-on-a-type">10.2 Implementing a Trait on a Type</a> · std: <a href="https://doc.rust-lang.org/std/fmt/trait.Display.html"><code>fmt::Display</code></a></p>
|
|
|
|
<h3>The one rule that will bite you</h3>
|
|
|
|
<p>You may write <code>impl SomeTrait for SomeType</code> only if <strong>the trait or the type is yours</strong>. <code>Display</code> for your <code>Task</code>: fine, the type is yours. Your own trait for <code>Vec<T></code>: fine, the trait is yours. <code>Display</code> for <code>Vec<String></code>: rejected — both belong to the standard library. This is the <em>orphan rule</em>, and it exists so no other crate can change what your code already does. <span class="cite">(<a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html#implementing-a-trait-on-a-type">Book 10.2, "coherence"</a>)</span></p>
|
|
|
|
<h2>Part 3 — Trait bounds: generics that are allowed to do something</h2>
|
|
|
|
<p>A generic <code><T></code> on its own means "any type at all" — and a function that accepts any type may do almost nothing with it, because the compiler has no idea what it can do. Watch it fail:</p>
|
|
|
|
<pre><code>fn hottest<T>(items: &[T]) -> Option<&T> {
|
|
items.iter().max_by(|a, b| a.celsius().total_cmp(&b.celsius()))
|
|
}</code></pre>
|
|
|
|
<pre><code>error[E0599]: no method named `celsius` found for reference `&&T` in the current scope
|
|
--> src/main.rs:46:34
|
|
|
|
|
46 | items.iter().max_by(|a, b| a.celsius().total_cmp(&b.celsius()))
|
|
| ^^^^^^^ method not found in `&&T`
|
|
|
|
|
= 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>The fix is a <strong>bound</strong> — a promise demanded of the caller's type:</p>
|
|
|
|
<pre><code>fn hottest<T: Reading>(items: &[T]) -> Option<&T> {
|
|
items.iter().max_by(|a, b| a.celsius().total_cmp(&b.celsius()))
|
|
}</code></pre>
|
|
|
|
<pre><code>5. hottest: attic [28.0C]</code></pre>
|
|
|
|
<p>Read <code><T: Reading></code> as: <em>T can be any type, as long as it implements <code>Reading</code></em>. Inside the function you may now use every method the trait promises, and nothing else. Both sides get a guarantee, both checked at compile time, and no lookup happens at runtime.</p>
|
|
|
|
<p>Three spellings of the same idea, so you recognise all of them in other people's code:</p>
|
|
|
|
<pre><code>fn show<T: Reading>(r: &T) // bound in the angle brackets
|
|
fn show(r: &impl Reading) // same thing, shorter
|
|
fn show<T>(r: &T) where T: Reading // same thing, for long bound lists</code></pre>
|
|
|
|
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html#traits-as-parameters">10.2 Traits as Parameters</a> · <a href="https://doc.rust-lang.org/stable/book/ch10-01-syntax.html">10.1 Generic Data Types</a></p>
|
|
|
|
<h2>Part 4 — An error type is a type with two traits on it</h2>
|
|
|
|
<p>Your <code>tasks</code> crate reports failures as <code>String</code>. That works and 0003 asked for it deliberately, because the real answer needs Part 1 to Part 3. Here it is.</p>
|
|
|
|
<p>Start with what you already know how to write — an enum, one variant per way of failing, carrying whatever the caller needs:</p>
|
|
|
|
<pre><code>#[derive(Debug)]
|
|
enum SensorError {
|
|
Empty,
|
|
NotANumber(ParseFloatError),
|
|
OutOfRange(f64),
|
|
}</code></pre>
|
|
|
|
<p>Compare that with <code>String</code>. A caller can <code>match</code> on this and react differently per case; it cannot match on prose. The bad reading is still <em>in</em> the value, so the message can be built later, at the edge of the program. And you cannot typo a variant — <code>"out of rnage"</code> compiles, <code>OutOfRnage</code> does not.</p>
|
|
|
|
<p>Then two traits turn it from "an enum" into "an error":</p>
|
|
|
|
<pre><code>impl fmt::Display for SensorError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
SensorError::Empty => write!(f, "no reading given"),
|
|
SensorError::NotANumber(e) => write!(f, "not a number: {}", e),
|
|
SensorError::OutOfRange(v) => write!(f, "{} is outside -90..60", v),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for SensorError {} // std::error::Error</code></pre>
|
|
|
|
<p><code>Display</code> is the human sentence — Part 2, applied. <code>Error</code> is an empty impl: it adds no code, it only <em>marks</em> the type as an error so it fits everywhere the ecosystem expects one. But it does demand something. Delete the <code>Display</code> impl and keep <code>impl Error</code>:</p>
|
|
|
|
<pre><code>error[E0277]: `SensorError` doesn't implement `std::fmt::Display`
|
|
--> src/main.rs:13:16
|
|
|
|
|
13 | impl Error for SensorError {}
|
|
| ^^^^^^^^^^^ unsatisfied trait bound
|
|
|
|
|
help: the trait `std::fmt::Display` is not implemented for `SensorError`</code></pre>
|
|
|
|
<p><code>Error</code> requires <code>Display</code> and <code>Debug</code> — a trait can demand other traits, the same way a function demands bounds. That is why <code>#[derive(Debug)]</code> sits on the enum. <span class="cite">(<a href="https://doc.rust-lang.org/std/error/trait.Error.html">std: <code>Error</code></a> — "Errors must describe themselves through the <code>Display</code> and <code>Debug</code> traits")</span></p>
|
|
|
|
<h3><code>From</code>: a trait you have been using since chapter 1</h3>
|
|
|
|
<p>Yes — <code>From</code> is another trait, and it lives in the standard library. Its whole definition is one required method:</p>
|
|
|
|
<pre><code>trait From<T> {
|
|
fn from(value: T) -> Self; // build a Self out of a T
|
|
}</code></pre>
|
|
|
|
<p>You have called it in every lesson so far without knowing it had a name:</p>
|
|
|
|
<pre><code>let s = String::from("hi"); // this IS From: impl From<&str> for String, in std</code></pre>
|
|
|
|
<p>So read the impl below as an English sentence — <em>"here is how to build a <code>SensorError</code> out of a <code>ParseFloatError</code>"</em>:</p>
|
|
|
|
<pre><code>impl From<ParseFloatError> for SensorError {
|
|
fn from(e: ParseFloatError) -> SensorError {
|
|
SensorError::NotANumber(e)
|
|
}
|
|
}</code></pre>
|
|
|
|
<p>It is an ordinary function with a wrapper around it. You can call it by hand, and nothing magic happens:</p>
|
|
|
|
<pre><code>let e: ParseFloatError = "nope".parse::<f64>().unwrap_err();
|
|
let wrapped: SensorError = SensorError::from(e); // just a function call</code></pre>
|
|
|
|
<h4>So why bother writing it?</h4>
|
|
|
|
<p>Because <code>?</code> calls it for you. This is the whole point. <code>?</code> does not simply hand the error to your caller — it converts it first:</p>
|
|
|
|
<pre><code>let value = thing()?;
|
|
|
|
// what the compiler writes for you:
|
|
let value = match thing() {
|
|
Ok(v) => v,
|
|
Err(e) => return Err(From::from(e)), // <- YOUR impl runs here
|
|
};</code></pre>
|
|
|
|
<p>These three functions are therefore the same function. Same output, three spellings:</p>
|
|
|
|
<pre><code>fn by_hand(text: &str) -> Result<f64, SensorError> {
|
|
match text.parse::<f64>() {
|
|
Ok(v) => Ok(v),
|
|
Err(e) => Err(SensorError::from(e)), // call it yourself
|
|
}
|
|
}
|
|
|
|
fn with_into(text: &str) -> Result<f64, SensorError> {
|
|
match text.parse::<f64>() {
|
|
Ok(v) => Ok(v),
|
|
Err(e) => Err(e.into()), // `.into()` is From from the other side
|
|
}
|
|
}
|
|
|
|
fn with_question(text: &str) -> Result<f64, SensorError> {
|
|
Ok(text.parse::<f64>()?) // `?` calls it for you
|
|
}</code></pre>
|
|
|
|
<pre><code>1. Err(NotANumber(ParseFloatError { kind: Invalid }))
|
|
2. Err(NotANumber(ParseFloatError { kind: Invalid }))
|
|
3. Err(NotANumber(ParseFloatError { kind: Invalid }))</code></pre>
|
|
|
|
<p><code>e.into()</code> and <code>SensorError::from(e)</code> are the same trait read in opposite directions: <code>from</code> starts from the destination type, <code>into</code> starts from the value you hold. Implement <code>From</code> and you get <code>into</code> for free — you never write an <code>Into</code> impl.</p>
|
|
|
|
<h4>What it looks like when the impl is missing</h4>
|
|
|
|
<p>Delete the <code>impl From</code> and the compiler names precisely what is absent:</p>
|
|
|
|
<pre><code>error[E0277]: `?` couldn't convert the error to `SensorError`
|
|
--> src/main.rs:28:27
|
|
|
|
|
27 | fn with_question(text: &str) -> Result<f64, SensorError> {
|
|
| ------------------------ expected `SensorError` because of this
|
|
28 | Ok(text.parse::<f64>()?)
|
|
| --------------^ the trait `From<ParseFloatError>` is not implemented for `SensorError`
|
|
| |
|
|
| this can't be annotated with `?` because it has type `Result<_, ParseFloatError>`</code></pre>
|
|
|
|
<p>"the trait <code>From<X></code> is not implemented for <code>YourError</code>" always means the same thing: <em>write the recipe from X to YourError</em>. (Write the same code with an annotated <code>let value: f64 = text.parse()?;</code> and the report arrives as <code>E0271</code> instead, pointing at the same missing impl.)</p>
|
|
|
|
<h4>You already depend on this — in <code>command.rs</code>, line 13</h4>
|
|
|
|
<pre><code>fn parse(args: &[String]) -> Result<Command, String> {
|
|
let first = args.first().ok_or("No Arguments Found")?;
|
|
// ^^^^^^^^^^^^^^^^^^ this is a &str, not a String
|
|
}</code></pre>
|
|
|
|
<p><code>ok_or("No Arguments Found")</code> produces <code>Result<_, &str></code>, and your function promises <code>Result<_, String></code>. Two different types — the same mismatch as above. It compiled because the standard library already ships <code>impl From<&str> for String</code>, and <code>?</code> found it. Verified:</p>
|
|
|
|
<pre><code>$ cargo run
|
|
Err("No Arguments Found") // a String, converted on the way out</code></pre>
|
|
|
|
<p>That is why the mechanism was invisible in 0003: std had written the impl you needed. The moment your own error type appears, you write it.</p>
|
|
|
|
<p>And that is what keeps a deep call stack readable — every layer writes a bare <code>?</code>, and each error type carries its own recipe for becoming the layer above.</p>
|
|
|
|
<p>The finished function, with all three failure paths and one bare <code>?</code> doing the conversion:</p>
|
|
|
|
<pre><code>fn parse_reading(text: &str) -> Result<f64, SensorError> {
|
|
if text.is_empty() {
|
|
return Err(SensorError::Empty);
|
|
}
|
|
let value: f64 = text.parse()?; // ParseFloatError becomes SensorError here
|
|
if value < -90.0 || value > 60.0 {
|
|
return Err(SensorError::OutOfRange(value));
|
|
}
|
|
Ok(value)
|
|
}</code></pre>
|
|
|
|
<pre><code>1. Ok(21.5)
|
|
2. not a number: invalid float literal
|
|
3. 900 is outside -90..60
|
|
4. no reading given</code></pre>
|
|
|
|
<p class="cite">Book: <a href="https://doc.rust-lang.org/stable/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator">9.2 The <code>?</code> operator</a> · std: <a href="https://doc.rust-lang.org/std/convert/trait.From.html"><code>From</code></a></p>
|
|
|
|
<h3>Where the error meets the user</h3>
|
|
|
|
<p>One trait object is worth knowing before you touch your CLI. <code>Box<dyn Error></code> means "some value on the heap that implements <code>Error</code>, decided at runtime" — the escape hatch when a function can fail in unrelated ways and you do not want an enum listing them all. <code>main</code> may return it:</p>
|
|
|
|
<pre><code>fn main() -> Result<(), Box<dyn Error>> {
|
|
let ok = parse_reading("18.25")?;
|
|
println!("5. ? gave us {}", ok);
|
|
let boom = parse_reading("nope")?; // fails here
|
|
println!("never printed {}", boom);
|
|
Ok(())
|
|
}</code></pre>
|
|
|
|
<pre><code>5. ? gave us 18.25
|
|
Error: NotANumber(ParseFloatError { kind: Invalid })
|
|
$ echo $?
|
|
1</code></pre>
|
|
|
|
<p>The exit status is right, and <code>?</code> in <code>main</code> is genuinely useful in a script or a test binary. But look at the message: <code>NotANumber(ParseFloatError { kind: Invalid })</code>. That is <strong>Debug</strong>, not your carefully written <code>Display</code> — <code>main</code>'s reporting uses <code>{:?}</code>. For a CLI a human runs, you want your own sentence, so you handle it yourself at the top:</p>
|
|
|
|
<pre><code>match parse_reading(text) {
|
|
Ok(v) => println!("reading {:.1}C", v),
|
|
Err(e) => {
|
|
eprintln!("error: {}", e); // stderr, and Display
|
|
process::exit(1);
|
|
}
|
|
}</code></pre>
|
|
|
|
<pre><code>$ cargo run -- 21.5
|
|
reading 21.5C
|
|
$ echo $?
|
|
0
|
|
$ cargo run -- warm
|
|
error: not a number: warm
|
|
$ echo $?
|
|
1</code></pre>
|
|
|
|
<p><code>eprintln!</code> is <code>println!</code> aimed at stderr; <code>process::exit(1)</code> sets the status a caller reads. Those two lines and the missing <code>?</code> are the entirety of what your <code>main.rs</code> is short of.</p>
|
|
|
|
<h2>Retrieval — before the drill</h2>
|
|
|
|
<p>Answer from memory. Scrolling up to check first is the one way to waste these. Some questions are about older topics on purpose — mixing them is what makes any of it stick.</p>
|
|
|
|
<div class="q" data-type="recall" data-topic="Traits">
|
|
<p class="topic">Traits</p>
|
|
<p class="prompt">In a trait definition, what is the difference between a method that ends with a semicolon and one that ends with a block?</p>
|
|
<button class="reveal-btn">Show answer</button>
|
|
<div class="answer hidden">Semicolon = <strong>required</strong>: every implementor must write that method. Block = <strong>default</strong>: implementors get that body for free and may override it. A default may call the trait's required methods.</div>
|
|
<div class="grade hidden">
|
|
<button data-grade="hit">Got it</button>
|
|
<button data-grade="miss">Missed it</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="mcq" data-topic="Display">
|
|
<p class="topic">Display</p>
|
|
<p class="prompt">You wrote <code>impl fmt::Display for Task</code>. Which of these does that also give you, with no extra code?</p>
|
|
<div class="options">
|
|
<button class="opt" data-correct="false"><code>task.clone()</code> returning an owned copy of it</button>
|
|
<button class="opt" data-correct="true"><code>task.to_string()</code> returning an owned String of it</button>
|
|
<button class="opt" data-correct="false"><code>task.debug()</code> returning an owned dump of it</button>
|
|
</div>
|
|
<div class="explain hidden"><code>ToString</code> is implemented by the standard library for every type that implements <code>Display</code>. <code>Clone</code> and <code>Debug</code> are separate traits, both obtained by <code>#[derive]</code>.</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="recall" data-topic="Error handling">
|
|
<p class="topic">Error handling</p>
|
|
<p class="prompt">Your function returns <code>Result<T, MyError></code> and calls something that fails with <code>io::Error</code>. You want a bare <code>?</code> to work. What must you write?</p>
|
|
<button class="reveal-btn">Show answer</button>
|
|
<div class="answer hidden"><code>impl From<io::Error> for MyError</code>. The <code>?</code> operator calls <code>From::from</code> on the error as it returns, so any error type with a <code>From</code> impl into yours converts automatically.</div>
|
|
<div class="grade hidden">
|
|
<button data-grade="hit">Got it</button>
|
|
<button data-grade="miss">Missed it</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="recall" data-topic="Generics">
|
|
<p class="topic">Generics</p>
|
|
<p class="prompt">Why does <code>fn longest<T>(a: &T, b: &T)</code> refuse to compare <code>a</code> and <code>b</code> with <code>></code>, and what is the smallest fix?</p>
|
|
<button class="reveal-btn">Show answer</button>
|
|
<div class="answer hidden">Unbounded <code>T</code> promises nothing, so no methods or operators are available on it. Add the bound that provides comparison: <code>fn longest<T: PartialOrd>(..)</code>. The error you would see is <code>E0369</code>/<code>E0599</code>, naming the missing trait.</div>
|
|
<div class="grade hidden">
|
|
<button data-grade="hit">Got it</button>
|
|
<button data-grade="miss">Missed it</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="mcq" data-topic="Ownership">
|
|
<p class="topic">Ownership</p>
|
|
<p class="prompt">In <code>fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result</code>, why is the first parameter <code>&self</code> rather than <code>self</code>?</p>
|
|
<div class="options">
|
|
<button class="opt" data-correct="false">Printing a value has to consume the value being printed</button>
|
|
<button class="opt" data-correct="true">Printing a value must not consume the value being printed</button>
|
|
<button class="opt" data-correct="false">Printing a value should always copy the value being printed</button>
|
|
</div>
|
|
<div class="explain hidden">A consuming <code>self</code> would move the value into <code>println!</code>, making <code>println!("{}", t)</code> the last thing you could ever do with <code>t</code>. Chapter 4's rules, deciding your API shape — the same point 0004 made about <code>into_name</code>.</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="recall" data-topic="Enums">
|
|
<p class="topic">Enums</p>
|
|
<p class="prompt">Name two concrete advantages an error <code>enum</code> has over a <code>String</code> error, as your <code>tasks</code> crate uses today.</p>
|
|
<button class="reveal-btn">Show answer</button>
|
|
<div class="answer hidden">Any two of: the caller can <code>match</code> per failure case instead of parsing prose; the data that failed is carried in the variant, so the message is built at the edge; a misspelled variant will not compile whereas a misspelled message will; adding a variant makes the compiler list every place that must handle it.</div>
|
|
<div class="grade hidden">
|
|
<button data-grade="hit">Got it</button>
|
|
<button data-grade="miss">Missed it</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="q" data-type="recall" data-topic="Modules">
|
|
<p class="topic">Modules & paths</p>
|
|
<p class="prompt">You add <code>impl fmt::Display for Task</code> in <code>src/task.rs</code>. What does <code>main.rs</code> have to import to print a task with <code>{}</code>?</p>
|
|
<button class="reveal-btn">Show answer</button>
|
|
<div class="answer hidden">Only <code>Task</code> itself. <code>Display</code> is already in scope everywhere <code>println!</code> is usable, because the macro refers to it by full path. The "bring the trait into scope" rule applies when <em>you</em> call a trait method directly — e.g. <code>use std::io::Write;</code> before calling <code>.write_all()</code>.</div>
|
|
<div class="grade hidden">
|
|
<button data-grade="hit">Got it</button>
|
|
<button data-grade="miss">Missed it</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="summary"><div id="summary-body"></div><p id="summary-total"></p>
|
|
<button id="report-btn">Copy report</button><pre id="report-output" class="hidden"></pre></div>
|
|
|
|
<h2>The drill — 20 minutes, your own crate</h2>
|
|
|
|
<div class="callout">
|
|
<strong>Type it, do not paste it.</strong> The code above is a sensor in a different project; none of it fits <code>tasks</code> unchanged. Keep <a href="../reference/rust-syntax.html">the syntax reference</a> open — looking syntax up is free, copying answers is not.
|
|
</div>
|
|
|
|
<p>Work in <code>~/learn-rust/tasks</code>. Three steps, each with its own check. Run <code>cargo test</code> at the end: all 17 must still pass, because you are not changing the library's contract.</p>
|
|
|
|
<h3>Step 1 — <code>Display for Task</code></h3>
|
|
|
|
<p>Your <code>main.rs</code> builds the list line by hand inside a closure. Move that decision to the type: implement <code>fmt::Display</code> for <code>Task</code> in <code>src/task.rs</code>, producing exactly the format the spec prints — <code>1 [todo] buy milk (medium)</code> — then reduce the <code>list</code> arm to printing each task with <code>{}</code>.</p>
|
|
|
|
<p>Check: <code>cargo run -- add x</code> still works, and <code>list</code>'s output format has not changed.</p>
|
|
|
|
<details>
|
|
<summary>Stuck for ten minutes on the signature?</summary>
|
|
<p>The file needs <code>use std::fmt;</code> at the top. The impl block goes anywhere in <code>task.rs</code>, and the one method is <code>fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result</code>. The body is a single <code>write!(f, ..)</code> with no semicolon; inside it you can call <code>self.status.label()</code> just as <code>main.rs</code> does now.</p>
|
|
</details>
|
|
|
|
<h3>Step 2 — no <code>unwrap</code>, no panic</h3>
|
|
|
|
<p>Move the body of <code>main</code> into a second function that returns <code>Result<(), String></code>, so <code>Command::parse</code>, <code>complete</code> and <code>remove</code> can all be reached with <code>?</code> instead of <code>unwrap</code> and nested matches. <code>main</code> keeps only: collect the args, call it, and deal with the error. While you are there, <code>remove</code> prints nothing today — make it say <code>removed <id></code>.</p>
|
|
|
|
<p>Check: <code>grep unwrap src/main.rs</code> finds nothing.</p>
|
|
|
|
<details>
|
|
<summary>Stuck for ten minutes on the shape?</summary>
|
|
<p><code>fn run(args: &[String], store: &mut Store) -> Result<(), String></code>. Its first line can be <code>match Command::parse(args)? { .. }</code> — the <code>?</code> lands on <code>parse</code>, so the match arms deal with <code>Command</code> values, not <code>Result</code>s. Every arm ends in <code>()</code>, and the function's last line is <code>Ok(())</code>. This works with no <code>From</code> impl because every error in play is already <code>String</code>.</p>
|
|
</details>
|
|
|
|
<h3>Step 3 — the contract from 0003</h3>
|
|
|
|
<p>In <code>main</code>, report the failure on stderr with your own message and exit with status 1. Nothing else changes.</p>
|
|
|
|
<p>Check — all three must hold:</p>
|
|
|
|
<pre><code>$ cargo run --quiet -- fly ; echo $?
|
|
error: no valid commands
|
|
1
|
|
$ cargo run --quiet -- done 9 ; echo $?
|
|
error: id not found
|
|
1
|
|
$ cargo run --quiet -- add "buy milk" 2>/dev/null ; echo $?
|
|
added task 1
|
|
0</code></pre>
|
|
|
|
<details>
|
|
<summary>Stuck for ten minutes on the last two lines?</summary>
|
|
<p><code>use std::process;</code> at the top. Then <code>if let Err(e) = run(&args, &mut store) { .. }</code> is enough — inside it, <code>eprintln!("error: {}", e);</code> followed by <code>process::exit(1);</code>. The third check passes automatically once errors leave stdout: redirecting stderr to <code>/dev/null</code> must not swallow real output.</p>
|
|
</details>
|
|
|
|
<h3>Then stop</h3>
|
|
|
|
<p>Converting <code>String</code> errors into a proper <code>TaskError</code> enum with <code>Display</code>, <code>Error</code> and <code>From</code> is the obvious next move, and it is deliberately <em>not</em> in this drill — it touches all four files and it is the next lesson. Get these three green first.</p>
|
|
|
|
<h2>The five sentences worth keeping</h2>
|
|
|
|
<ol>
|
|
<li>A <strong>trait</strong> is a list of promised methods; <code>impl Trait for Type</code> is a type keeping that promise.</li>
|
|
<li><code>{}</code> is <code>Display</code> and nothing else — you write it by hand, and <code>to_string()</code> comes free with it. <code>{:?}</code> is <code>Debug</code>, which you derive.</li>
|
|
<li>A bare <code><T></code> can do nothing; <code><T: Trait></code> can do exactly what the trait promises.</li>
|
|
<li>An <strong>error type</strong> is an enum plus <code>Display</code> plus the empty <code>impl Error</code>; <code>?</code> converts between error types by calling <code>From</code>.</li>
|
|
<li>Failures a program can expect belong in <code>Result</code>, on <strong>stderr</strong>, with <strong>exit 1</strong>. <code>unwrap</code> is for the cases you have proved impossible.</li>
|
|
</ol>
|
|
|
|
<footer>
|
|
<p><strong>Primary source:</strong> <a href="https://doc.rust-lang.org/stable/book/ch10-02-traits.html">The Rust Book, ch. 10.2 — Traits: Defining Shared Behavior</a>. Read it in full; it is the highest-value chapter left in the book for your mission, because every library you will touch in a backend job (serde, axum, tokio) is a pile of traits. Then skim <a href="https://doc.rust-lang.org/std/error/trait.Error.html">std::error::Error</a> for the two-sentence definition of what an error is.</p>
|
|
<p><strong>Previous:</strong> <a href="0004-structs-enums-packages.html">0004 — Structs, enums, packages</a> · <a href="0003-build-a-task-cli.html">0003 — Task CLI project</a> · <strong>Reference:</strong> <a href="../reference/rust-syntax.html">Rust syntax reference</a> (new sections: <a href="../reference/rust-syntax.html#traits">Traits & generics</a>, <a href="../reference/rust-syntax.html#error-types">Error types</a>)</p>
|
|
<p><strong>Ask me things.</strong> If a paragraph did not land, say which one — vague explanation is my fault, not yours, and it is far cheaper to fix here than in the middle of the drill. Bring me your compiler errors verbatim; reading them together is the fastest way to make them stop being scary.</p>
|
|
</footer>
|
|
|
|
<script src="../assets/quiz.js"></script>
|
|
</body>
|
|
</html>
|