In this lesson, you will learn how to distinguish between errors your program can recover from and those that should cause it to stop immediately.
Recoverable vs. Unrecoverable
In Rust, we categorize problems into two types:
- Unrecoverable errors: These are catastrophic failures, like trying to access an index outside of an array's bounds. In these cases, we use a
panic!, which crashes the program. - Recoverable errors: These are scenarios where the program might fail, but can decide what to do next. For example, failing to open a file because it doesn't exist. We don't want the program to crash; we want to handle the situation.
The Result Enum
To handle recoverable errors, Rust uses the Result enum. You've already learned about enums and pattern matching, so this will look familiar! A Result is defined like this:
enum Result<T, E> {
Ok(T),
Err(E),
}
Ok(T): This represents success and holds the valueT.Err(E): This represents a failure and holds the error informationE.
When a function might fail, it returns a Result. You then use a match statement to decide how to proceed.
A Concrete Example
Imagine a function that divides two numbers. If the divisor is zero, we can't perform the math. Instead of crashing, we return a Result:
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
}
fn main() {
let result = divide(10.0, 0.0);
match result {
Ok(value) => println!("The result is {}", value),
Err(e) => println!("Error encountered: {}", e),
}
}
By returning a Result, we force the person calling our function to acknowledge the possibility of failure. They must decide whether to print an error message, try again, or take some other corrective action. This makes your programs significantly more robust than those that simply crash when things go wrong.
Key Takeaway: Use panic! for unrecoverable state violations and the Result enum to gracefully handle expected failures during program execution.