We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 18 of 24
Boxed errors and downcasting
A helper hands you back errors of two completely different types, both erased
behind Box<dyn Error>. Recover the distinction.
fn read(input: &str) -> Result<i32, Box<dyn Error>>
pub fn triage(inputs: Vec<String>) -> Vec<String>
read parses the input as i32 and then checks it is in 0..=100. A
non-numeric input fails with std’s ParseIntError; an out-of-range one fails
with your own RangeError.
triage maps each input to:
-
"ok:{n}"on success, -
"range"if the error is aRangeError, -
"parse"if it is aParseIntError, -
"other"for anything else.
Read the starter’s E0782 first
error[E0782]: expected a type, found a trait
|
| fn read(input: &str) -> Result<i32, Error> {
| ^^^^^
In edition 2015 you could write a bare trait name where a type was expected
and Rust would quietly read it as a trait object. That was a mistake — it made
Box<Error> and Box<SomeStruct> look identical while behaving completely
differently — so dyn became mandatory, and in edition 2024 the bare form is
a hard error, not a warning.
But dyn Error alone will not compile either, and the reason is worth
understanding rather than memorising. dyn Error is a type whose size is not
known at compile time: it could be a one-byte struct or a hundred-byte one,
depending on which error is in there at runtime. Rust needs a size to lay out a
Result, so an unsized type has to sit behind a pointer:
Box<dyn Error> // owned, heap
&dyn Error // borrowed
Box<dyn Error> it is.
Why ? “just works” on it
let n: i32 = input.trim().parse()?;
parse gives Result<i32, ParseIntError>; the function returns
Result<i32, Box<dyn Error>>; and ? converts because std provides
impl<E: Error + 'static> From<E> for Box<dyn Error>
Recall from the previous item that you cannot write that blanket impl for
your own enum — E0119, conflicting with impl<T> From<T> for T. Box<dyn Error> gets away with it precisely because it is a different type from every
error it swallows. That is the trade in one sentence: you give up the
concrete type, and in exchange ? accepts everything.
There is even an impl for &str and String, so Err("something went wrong")? compiles in a function returning Box<dyn Error>. Convenient, and a
stringly-typed contract if you lean on it.
Getting the type back: downcast_ref
if let Some(r) = e.downcast_ref::<RangeError>() {
// `r` is a `&RangeError` — full access to its fields
}
downcast_ref::<T>() returns Option<&T>: Some if the erased value really
is a T, None otherwise. It is a runtime type test, backed by TypeId,
which is why it requires T: 'static — a type that borrows cannot have a
stable identity.
Three shapes exist: downcast_ref (borrow), downcast_mut (mutable borrow),
and downcast (consume the Box, returning Result<Box<T>, Box<dyn Error>>
so you get the box back if you guessed wrong).
Note the cost. Distinguishing two error kinds took two runtime type tests, and
nothing tells you when you have covered them all — add a third error type to
read and this match still compiles, silently routing it to "other". With
an enum, the compiler would have stopped you. That is the real tradeoff
between Box<dyn Error> and an error enum, and no amount of style preference
changes it.
The gate denies clippy::unwrap_used here so you cannot paper over the
Option that downcast_ref returns.
Notes
-
Box<dyn Error>is notSend. It cannot cross athread::spawnboundary, and learners who meet that for the first time inside a threading exercise usually blame threads. The fix isBox<dyn Error + Send + Sync + 'static>, which is also whatanyhow::Erroruses internally. -
Match guards let you write this as one
match—Err(e) if e.downcast_ref::<RangeError>().is_some() => …— with the arms tried in order. Anif letchain works just as well. -
borrowed_boxis the lint for&Box<dyn Error>in a signature: take&dyn Errorinstead, one less indirection and a more general API. -
3.5and9999999999are bothParseIntError(wrong shape and overflow), so one downcast catches both.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.