We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 16 of 24
Error chains and source()
Walk an error’s cause chain and render it.
pub fn chain(depth: usize) -> Vec<String>
pub fn rendered(depth: usize) -> String
build(depth) is given. It produces a ConfigError that is one, two or three
layers deep:
-
layer 1 —
ConfigError, Displayfailed to load configuration -
layer 2 —
FieldError, Displaykey `port` is not an integer -
layer 3 — a real
std::num::ParseIntErrorfrom"12x".parse::<i32>(), Displayinvalid digit found in string
chain returns every layer’s Display string, top-down. rendered joins
them with ": ". So chain(3) is
["failed to load configuration",
"key `port` is not an integer",
"invalid digit found in string"]
and rendered(3) is
failed to load configuration: key `port` is not an integer: invalid digit found in string
rendered(1) is just "failed to load configuration" — no trailing
separator.
Why this is the payoff of the last two items
Look at that rendered line. The first clause tells an operator what failed,
the second where, the third why. No single error type could have produced
it: the third layer was written by the standard library years ago and knows
nothing about config files, and the first knows nothing about integers. The
chain is what lets each layer say only its own part — which is exactly what
the Display convention from item 6.14 was preparing for.
This function is also, precisely, what anyhow‘s {:#} formatting gives you
for free. Writing it once by hand is how “anyhow is convenient” becomes
“anyhow saves me these nine lines”.
The starter’s E0599 is the one you will actually hit
error[E0599]: no method named `sources` found for struct `ConfigError`
|
70 | top.sources().map(|e| e.to_string()).collect()
| ^^^^^^^
help: there is a method `source` with a similar name
Error::sources() — the iterator that would make this a one-liner — is still
unstable on Rust 1.95 (error_iter, tracking issue #58520). It appears in
blog posts and in older documentation, so learners try it, and the error they
get is a plain “no such method” rather than “this is nightly-only”. Note that
rustc’s suggestion is useful but not a fix: source() returns a single
Option, not an iterator.
So you write the loop:
let mut cur = e.source();
while let Some(s) = cur {
// ...record s.to_string()...
cur = s.source();
}
source() returns Option<&(dyn Error + 'static)>. The loop stops when a
layer has no source. Do not stop it with a counter — case depth: 9 builds
the same three layers, and a loop that trusts depth will run off the end or
produce the wrong answer.
The borrow that catches people
E0716, “temporary value dropped while borrowed”:
let mut cur = build(depth).source(); // E0716
build(depth) creates a temporary ConfigError; .source() borrows from
it; the temporary is dropped at the end of the statement; the borrow outlives
it. Bind the error to a variable first, and everything the chain hands you
lives as long as that variable does:
let top = build(depth);
let mut cur = top.source();
Building the string
chain(depth).join(": ") is the clean way, and it is what the reference
solution does. Two notes for when you write this in real code:
-
msg.push_str(&format!("{s}: "))allocates a wholeStringjust to copy it into another one. Clippy has a lint for it (format_push_string, pedantic). The fix iswrite!:use std::fmt::Write as _; let _ = write!(out, "{s}: "); // writing to a String cannot failThe
use … as _imports the trait for its methods without bringing the nameWriteinto scope, which matters becausestd::io::Writeexists too and they collide. -
single_char_add_stris the sibling lint:s.push_str(":")should bes.push(':').
Notes
-
Backtracecapture lives behindError::provide(), which is still nightly. In stable code you either store aBacktracein your error type explicitly or you use a crate that does. -
The
Option<&(dyn Error + 'static)>return type reads oddly the first time.'staticbounds the erased type, not the borrow: it says the concrete error behind thedyncontains no borrowed data, which is what makesdowncast_refpossible later. -
clippy::missing_errors_docwould want a# Errorssection on every public function returningResult. Neither function here returns one — chain-walking is infallible.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.