- Choose correctly between a panic and a
Result - Handle a
Resultwithmatch,unwrap_orandexpect - Pass an error up to the caller with the
?operator - Create your own error enum that implements
Display
You ask the user for their age and they type abc. You want to read a file, but the file does not exist. These are not bugs in your program but expected situations — the program should respond to them without crashing. Rust asks you to show every possible failure in the types, and the compiler does not let you forget them.
Two kinds of errors
An unrecoverable error means there is a bug in the program: going past the end of an array, or a situation that “should never happen”. In that case Rust panics: it prints an error message and stops the thread. You can also call panic!("message") yourself. Recoverable errors, on the other hand, are returned with the Result<T, E> type and handled by the calling code.
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("division by zero: {a} / {b}");
}
a / b
}
fn main() {
println!("{}", divide(10, 2));
println!("{}", divide(1, 0));
println!("never printed");
}5 thread 'main' panicked at src/main.rs:3:9: division by zero: 1 / 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
index out of bounds: the len is 3 but the index is 10.The Result type
Result<T, E> is an enum with two variants: Ok(T) on success and Err(E) on failure. For example, the parse method, which turns text into a number, returns a Result, because the text may not be a number. With match we handle both cases separately:
fn main() {
let inputs = ["42", "abc", ""];
for input in inputs {
match input.parse::<i32>() {
Ok(n) => println!("{input:?} -> {}", n * 2),
Err(e) => println!("{input:?} -> error: {e}"),
}
}
}"42" -> 84 "abc" -> error: invalid digit found in string "" -> error: cannot parse integer from empty string
parse::<i32>(), the ::<i32> part says which type to convert to (people call it the “turbofish”). The error is printed with {e} as human-readable text.unwrap, expect and other shortcuts
fn main() {
let a: i32 = "7".parse().unwrap();
let b: i32 = "8".parse().expect("b must be a number");
let c: i32 = "x".parse().unwrap_or(0);
let d: i32 = "".parse().unwrap_or_default();
println!("{}", a + b + c + d);
}15
c and d, but the program did not stop: unwrap_or(0) and unwrap_or_default() returned a fallback value (0). 7 + 8 + 0 + 0 = 15.| Method | On Err | When to use |
|---|---|---|
match | your own code for each case | when you need full control |
? | returns the error to the caller | when the function itself returns Result |
unwrap_or(v) | returns v | when a sensible fallback exists |
expect("...") | panics with your message | when failure means a bug |
unwrap() | panics | only in examples and tests |
The ? operator
? is written after a Result. If the result is Ok, ? takes out the value and the code continues. If it is Err, the current function returns that error immediately. This way errors are passed up to the caller without writing a match at every step. The condition: the function itself must return a Result (or an Option).
use std::num::ParseIntError;
fn sum_pair(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x: i32 = a.trim().parse()?;
let y: i32 = b.trim().parse()?;
Ok(x + y)
}
fn main() {
println!("{:?}", sum_pair("10", " 32 "));
println!("{:?}", sum_pair("10", "ten"));
match sum_pair("5", "") {
Ok(total) => println!("total = {total}"),
Err(e) => println!("failed: {e}"),
}
}Ok(42)
Err(ParseIntError { kind: InvalidDigit })
failed: cannot parse integer from empty stringtrim() removes spaces at the edges. {:?} shows the error's inner structure, while {e} shows its readable message.use std::num::ParseIntError;
fn read_age(s: &str) -> Result<u8, ParseIntError> {
let age = match s.parse::<u8>() {
Ok(value) => value,
Err(e) => return Err(e),
};
Ok(age)
}use std::num::ParseIntError;
fn read_age(s: &str) -> Result<u8, ParseIntError> {
let age = s.parse::<u8>()?;
Ok(age)
}cargo clippy sees the version on the left, it suggests replacing it with ? (the question_mark lint).fn main() {
let n: i32 = "5".parse()?;
println!("{n}");
}use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let n: i32 = "5".parse()?;
println!("{n}");
Ok(())
}main returns nothing, so the compiler says: the ? operator can only be used in a function that returns Result or Option. main can return a Result too; on Err the error is printed and the program exits with a failure code.Your own error type
In a large program, errors deserve their own names. Write the kinds of errors as an enum and implement the Display trait for it, so the error is shown to the user as a readable message. The map_err method turns one error type into another — it is often used together with ?. (You will study traits in detail in lesson 8.)
use std::fmt;
#[derive(Debug)]
enum AgeError {
NotANumber,
TooOld(u32),
}
impl fmt::Display for AgeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AgeError::NotANumber => write!(f, "age must be a number"),
AgeError::TooOld(n) => write!(f, "{n} is too old"),
}
}
}
fn parse_age(s: &str) -> Result<u32, AgeError> {
let age: u32 = s.parse().map_err(|_| AgeError::NotANumber)?;
if age > 150 {
return Err(AgeError::TooOld(age));
}
Ok(age)
}
fn main() {
for s in ["15", "abc", "200"] {
match parse_age(s) {
Ok(age) => println!("ok: {age}"),
Err(e) => println!("error: {e} / {e:?}"),
}
}
}ok: 15 error: age must be a number / NotANumber error: 200 is too old / TooOld(200)
{e} uses our Display implementation, and {e:?} uses the text generated by #[derive(Debug)]. |_| is a short function that ignores its parameter (a closure, lesson 7).Key points
panic!stops the program on a bug; expected errors are returned asResult<T, E>.- A
Resultholds eitherOk(value)orErr(error)and is handled withmatch. unwrapandexpectpanic onErr; in production code prefer?,matchorunwrap_or.?unwrapsOkand returnsErrto the caller at once; it only works in functions that returnResultorOption.- For your own error type, write an
enum, implementDisplayfor it and convert errors withmap_err.
Check yourself
10 questions. Every correct answer earns XP.
? operator do when the result is Err?