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 parsing helper can fail for two reasons: the text is not an i32, or the
parsed number is outside the allowed range. Store both error types behind
Box<dyn Error>, then identify them in the caller.
Fix read's return type and implement triage:
fn read(input: &str) -> Result<i32, Box<dyn Error>>
pub fn triage(inputs: Vec<String>) -> Vec<String>
Required behavior
read first calls trim(), removing surrounding Unicode whitespace, then
parses a decimal i32. Parsing accepts an optional leading + or -
followed by ASCII digits. Empty text, internal spaces, decimal points and
values outside the i32 range produce ParseIntError.
If parsing succeeds, check whether the number is in 0..=100, inclusive.
Return the supplied RangeError for numbers outside that interval.
triage returns one label per input, in the original order. Keep duplicates;
an empty input vector produces an empty output vector.
Result from read |
Label | Example |
|---|---|---|
Success with value n |
"ok:{n}" |
" +007 " becomes "ok:7" |
RangeError |
"range" |
"101" or "-1" |
ParseIntError |
"parse" |
"3.5" or "2147483648" |
| Any other error type | "other" |
A fallback for future changes |
Format the parsed value, not the original text: "-0" becomes "ok:0".
The supplied read only produces the two named error types, but keep the
fallback arm in triage.
Why the return type needs Box
The starter uses Result<i32, Error>. In edition 2024 this produces E0782:
Error is a trait, not a concrete error type. Writing dyn Error identifies
a trait-object type, but it is unsized and cannot be stored directly as the
error parameter of Result.
Box<dyn Error> is a sized owning pointer. It can hold either error type
while giving read one return type. The concrete value still exists inside
the box; the caller initially sees it through the Error interface.
How ? converts the parse error
In let n: i32 = input.trim().parse()?;, parsing returns a ParseIntError
on failure. The standard library provides a conversion from an error type
implementing Error + 'static into Box<dyn Error>. Here, ? uses that
conversion and returns early. On success, it yields the parsed number.
The separate range check constructs RangeError and boxes it explicitly.
Both paths preserve the concrete error value, so the caller can inspect its
type later.
Inspecting the boxed error
e.downcast_ref::<T>() returns Some(&T) when the boxed value has exactly
type T, or None otherwise. It borrows the error without consuming it.
This complete example demonstrates the parse-error path:
use std::error::Error;
use std::num::ParseIntError;
fn parse_number(text: &str) -> Result<i32, Box<dyn Error>> {
Ok(text.parse()?)
}
fn main() {
match parse_number("hello") {
Ok(n) => println!("number: {n}"),
Err(e) => {
assert!(e.downcast_ref::<ParseIntError>().is_some());
assert!(e.downcast_ref::<std::io::Error>().is_none());
}
}
}
In triage, use the same kind of test for RangeError and ParseIntError.
Match guards with .is_some() are enough when you only need a label. Use
if let Some(error) = ... when you also need to inspect the error's data.
Downcasting checks the error itself. It does not search the errors returned
by Error::source(). If a wrapper contains a parse error, downcasting the
wrapper to ParseIntError returns None.
The target type must implement Error + 'static. This permits owned data
and static references; it does not mean the error value must live forever.
A type borrowing a shorter-lived value cannot satisfy this bound.
Choosing an error API
Boxed errors let a function propagate different error types through one interface. An error enum instead names a fixed set of cases. Matching every enum variant without a wildcard lets the compiler report newly added cases. With boxed errors, an unfamiliar type reaches your fallback at runtime.
For code that transfers an error between threads, the trait object also
needs Send. Sharing references across threads requires Sync.
Box<dyn Error + Send + Sync> is a common choice when both are needed,
provided the concrete errors meet those bounds. This exercise needs neither.
Submissions must compile, pass the tests and pass clippy -D warnings.
The starter also denies clippy::unwrap_used and clippy::expect_used;
handle the downcast result with a match, if let or .is_some().
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.