Lesson 11
Error Handling
Handle errors gracefully with Result<T, E>, the ? operator, and avoid panicking.
Result: Errors as Values
Rust doesn't have exceptions. Instead, functions that can fail return Result<T, E> -- an enum with two variants: Ok(value) for success and Err(error) for failure. The compiler forces you to handle both cases, so errors can't silently slip by.
enum Result<T, E> {
Ok(T),
Err(E),
}
Handling Results
You can handle a Result with match, unwrap() (panics on error -- avoid in production), unwrap_or(default), or expect("message"). For clean error propagation, the ? operator is your best friend -- it returns the error early if one occurs, or unwraps the success value.
The ? Operator
Place ? after any expression that returns a Result. If it's Ok, the value is extracted. If it's Err, the error is returned immediately from the enclosing function. This keeps your code clean and readable without hiding error paths.
Try it
use std::num::ParseIntError;
// A function that can fail -- returns Result
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n = s.parse::<i32>()?; // ? returns early on error
Ok(n * 2)
}
// Chaining multiple operations that can fail
fn parse_and_add(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x = a.parse::<i32>()?;
let y = b.parse::<i32>()?;
Ok(x + y)
}
fn main() {
// --- Basic Result handling with match ---
println!("--- Parsing Numbers ---");
let good: Result<i32, _> = "42".parse();
let bad: Result<i32, _> = "abc".parse();
match good {
Ok(n) => println!(" Parsed successfully: {}", n),
Err(e) => println!(" Failed to parse: {}", e),
}
match bad {
Ok(n) => println!(" Parsed successfully: {}", n),
Err(e) => println!(" Failed to parse: {}", e),
}
// --- Using helper methods ---
println!("\n--- Convenient Methods ---");
// unwrap_or: provide a default on error
let value = "not_a_number".parse::<i32>().unwrap_or(0);
println!(" unwrap_or(0): {}", value);
// unwrap_or_else: compute default lazily
let value = "100".parse::<i32>().unwrap_or_else(|_| -1);
println!(" Parsed '100': {}", value);
// --- Using the ? operator ---
println!("\n--- Using ? Operator ---");
match parse_and_double("21") {
Ok(result) => println!(" parse_and_double(\"21\") = {}", result),
Err(e) => println!(" Error: {}", e),
}
match parse_and_double("oops") {
Ok(result) => println!(" parse_and_double(\"oops\") = {}", result),
Err(e) => println!(" Error: {}", e),
}
// --- Chaining results ---
println!("\n--- Chaining Results ---");
match parse_and_add("10", "20") {
Ok(sum) => println!(" 10 + 20 = {}", sum),
Err(e) => println!(" Error: {}", e),
}
match parse_and_add("10", "nope") {
Ok(sum) => println!(" 10 + nope = {}", sum),
Err(e) => println!(" Error: {}", e),
}
// --- Practical example: safe division ---
println!("\n--- Safe Division ---");
println!(" 10 / 3 = {:?}", safe_divide(10.0, 3.0));
println!(" 10 / 0 = {:?}", safe_divide(10.0, 0.0));
}
fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}Challenge
Write a function that takes a string, parses it as a number, and returns Result<String, ParseIntError> with a message like "The number is: 42". Use the ? operator.
Previous: Pattern Matching
Next: Collections