Step 1 of 7 · Reading · ~3 min
Learn
Iterators, Result, Option
Result and Option: Custom Errors and ?
After this lesson you can give a program its own error type, let ? convert library errors into it, and shorten Option and Result pipelines.
In Rust Fundamentals a fallible function returned Result<T, String> and ? passed the error up. A String has no structure: callers can only compare text. Here the error is a type of its own, and ? converts between error types.
An error type is an enum
One variant per kind of failure, each carrying the data its message needs. Three impls finish it: Display is the message for a person, std::error::Error marks the type as an error (needs Debug and Display), and From<ParseIntError> tells ? how to turn the library's error into yours.
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum VolumeError {
NoEquals(String),
BadLevel(ParseIntError),
TooLoud(i32),
}
impl fmt::Display for VolumeError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
match self {
VolumeError::NoEquals(s) => write!(out, "no '=' in {:?}", s),
VolumeError::BadLevel(e) => write!(out, "bad level: {}", e),
VolumeError::TooLoud(n) => write!(out, "{} is above 11", n),
}
}
}
impl std::error::Error for VolumeError {}
impl From<ParseIntError> for VolumeError {
fn from(e: ParseIntError) -> Self {
VolumeError::BadLevel(e)
}
}
? converts through From
On Err(e), ? returns Err(From::from(e)), so a function returning Result<_, VolumeError> can use ? on a ParseIntError:
fn parse_level(text: &str) -> Result<i32, VolumeError> {
let at = text.find('=').ok_or_else(|| VolumeError::NoEquals(text.to_string()))?;
let level = text[at + 1..].parse::<i32>()?; // ParseIntError becomes VolumeError
if level > 11 {
return Err(VolumeError::TooLoud(level));
}
Ok(level)
}
| call | result |
|---|---|
parse_level("vol=7") | Ok(7) |
parse_level("vol=x") | bad level: invalid digit found in string |
parse_level("vol=40") | 40 is above 11 |
parse_level("vol 7") | no '=' in "vol 7" |
Without the From impl the parse line fails:
error[E0277]: `?` couldn't convert the error to `VolumeError`
the trait `std::convert::From<std::num::ParseIntError>` is not implemented for `VolumeError`
Combinators
ok_or(err)turnsNoneintoErr(err);ok_or_else(|| err)builds the error only when needed (useful when it allocates, liketo_string).map(f)changes the success value.and_then(f)is for anfthat itself returns anOptionorResult;mapwould nest them:Some(4i32).map(|n| n.checked_mul(1_000_000_000))isSome(None),and_thengivesNone.filter(pred)turns aSomeintoNonewhenpredis false:Some('7').filter(|c| c.is_alphabetic())isNone.- In a function returning
Option,let mid = name.split_whitespace().nth(1)?;returnsNoneearly when there is no second word. You cannot mix:?on anOptioninside a function returningResultdoes not compile; convert withok_orfirst.
Stopping at the first failure
An iterator of Results collects into one Result; the annotation picks that over a Vec of results.
let good: Result<Vec<i32>, ParseIntError> = "4 8 15".split(' ').map(|s| s.parse()).collect();
// Ok([4, 8, 15])
let bad: Result<Vec<i32>, ParseIntError> = "4 x 15 y".split(' ').map(|s| s.parse()).collect();
// Err(InvalidDigit): stops at "x"; "y" is never parsed
A specific enum lets callers match on the cause; the catch-all alternative is covered in Rust Advanced.
Common mistakes
- Collapsing every parse failure into one message with
map_err(|_| ...):xand99999999999print the same text. Wrapping the original error keeps std's message. - Plain
+,-,*oni32. The grader builds without optimisations, so2147483647 + 1panics and later output is lost. Usechecked_addand friends. - Guarding only division by zero.
checked_divalso returnsNonefori32::MIN / -1: that is overflow. - Splitting on
' '.add 3 4(two spaces) has an empty token and printsbad number: cannot parse integer from empty string.
Your exercise
Build a line calculator around your own CalcError: the enum, its Display, Error and From<ParseIntError> impls, then next_number and eval using ?. The starter reads the lines and counts failures. The likeliest slip is unchecked arithmetic, which panics on overflow.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…