Error Handling
## Learning Objectives
- Understand panic vs Result
- Master Option type
- Use Result for recoverable errors
- Propagate errors with ? operator
- Build robust error handling
## Error Handling Philosophy
Rust has two categories of errors:
- **Recoverable**: Use `Result`
- **Unrecoverable**: Use `panic!`
## Panic
### When to Panic
- Invariants are violated (bug in code)
- Example: trying to access out-of-bounds index
### Using panic
```rust
fn main() {
panic!("crash and burn");
}
```
### Panic Output
```text
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with RUST_BACKTRACE=1 for a backtrace.
```
### Backtrace
```bash
RUST_BACKTRACE=1 cargo run
```
### unwrap and expect
```rust
fn main() {
// unwrap: returns value or panics
let x = Some("value").unwrap();
// expect: returns value or panics with message
let x = Some("value").expect("Expected a value");
// On None/Err
let x: Option<&str> = None;
x.unwrap(); // Panics
}
```
### Result unwrap
```rust
fn main() {
// unwrap on Ok
let x: Result = Ok(42);
println!("{}", x.unwrap()); // 42
// unwrap on Err - panics
let x: Result = Err("error");
x.unwrap(); // Panics with "error"
// expect with message
let x: Result = Err("error");
x.expect("Should be Ok"); // Panics with "Should be Ok"
}
```
## Option Handling
### unwrap_or
```rust
fn main() {
let x = Some("value");
assert_eq!(x.unwrap_or("default"), "value");
let x: Option<&str> = None;
assert_eq!(x.unwrap_or("default"), "default");
}
```
### unwrap_or_else
```rust
fn main() {
let x: Option = None;
// Lazy evaluation
let result = x.unwrap_or_else(|| {
println!("Computing default...");
42
});
}
```
### map on Option
```rust
fn main() {
let x = Some(5);
let doubled = x.map(|n| n * 2); // Some(10)
let squared = x.map(|n| n * n); // Some(25)
let x: Option = None;
let doubled = x.map(|n| n * 2); // None
}
```
### and_then (flatmap)
```rust
fn main() {
let x = Some(5);
let result = x.and_then(|n| Some(n * 2)); // Some(10)
let x: Option = None;
let result = x.and_then(|n| Some(n * 2)); // None
}
```
### or and or_else
```rust
fn main() {
let x = Some(1);
let y = Some(2);
assert_eq!(x.or(y), Some(1)); // x is Some, keep it
let x: Option = None;
assert_eq!(x.or(y), Some(2)); // x is None, use y
let x = None;
let default = x.or_else(|| Some(42)); // Lazy default
}
```
### filter
```rust
fn main() {
let x = Some(10);
let result = x.filter(|n| n > 5); // Some(10)
let result = x.filter(|n| n > 15); // None
}
```
## Result Type
### Basic Usage
```rust
use std::fs::File;
fn main() {
let file = File::open("hello.txt");
match file {
Ok(_) => println!("File opened successfully"),
Err(e) => println!("Error opening file: {:?}", e),
}
}
```
### Result Methods
```rust
fn main() {
let x: Result = Ok(5);
x.is_ok(); // true
x.is_err(); // false
x.ok(); // Some(5)
x.err(); // None
x.unwrap_or(0); // 5
x.unwrap_or_else(|_| 0); // 5
x.map(|n| n * 2); // Ok(10)
x.map_err(|e| format!("Error: {}", e));
}
```
### Collecting Results
```rust
fn main() {
let results: Vec> = vec![Ok(1), Ok(2), Err("error")];
// Collect as Vec of values (fails if any Err)
// let values: Result, &str> = results.into_iter().collect();
// Partition into Ok and Err
let (oks, errs): (Vec<_>, Vec<_>) = results.into_iter().partition(Result::is_ok);
let values: Vec<_> = oks.into_iter().map(Result::unwrap).collect();
println!("{:?} {:?}", values, errs); // [1, 2] [Err("error")]
}
```
## The ? Operator
### The ? Operator Basics
```rust
use std::fs::File;
use std::io;
fn read_file() -> Result {
let mut file = File::open("hello.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
```
### ? with Option
```rust
fn first_word(s: &str) -> Option<&str> {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return Some(&s[0..i]);
}
}
None
}
fn main() {
let text = "hello world";
let first = first_word(text).ok_or("No first word")?;
println!("{}", first);
}
```
### Chaining ?
```rust
use std::fs::File;
use std::io;
use std::io::Read;
fn read_file_contents(path: &str) -> Result {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
match read_file_contents("hello.txt") {
Ok(contents) => println!("{}", contents),
Err(e) => eprintln!("Error: {:?}", e),
}
}
```
### From Trait for Conversion
```rust
use std::fs::File;
use std::io;
use std::num::ParseIntError;
fn read_number() -> Result {
let s = "42";
let n: i32 = s.parse()?; // ParseIntError
Ok(n)
}
```
## Custom Error Types
### Define Error Enum
```rust
use std::fmt;
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MyError::Io(e) => write!(f, "IO error: {}", e),
MyError::Parse(e) => write!(f, "Parse error: {}", e),
}
}
}
impl From for MyError {
fn from(err: std::io::Error) -> Self {
MyError::Io(err)
}
}
impl From for MyError {
fn from(err: std::num::ParseIntError) -> Self {
MyError::Parse(err)
}
}
```
### Using Custom Errors
```rust
fn read_and_parse(path: &str) -> Result {
let contents = std::fs::read_to_string(path)?;
let n: i32 = contents.trim().parse()?;
Ok(n)
}
```
## Best Practices
### Don't Use unwrap in Library Code
```rust
// Bad: library code that panics
pub fn get(&self, key: &str) -> &str {
self.map.get(key).unwrap() // Panics if missing
}
// Good: return Result
pub fn get(&self, key: &str) -> Option<&str> {
self.map.get(key)
}
```
### Use expect for Known Values
```rust
fn main() {
// When you're certain value exists
let x = [1, 2, 3];
let first = x.first().expect("Array should not be empty");
}
```
### Propagate Errors
```rust
use std::io;
fn read_file(path: &str) -> io::Result {
let mut file = io::File::open(path)?; // Propagate error
let mut contents = String::new();
file.read_to_string(&mut contents)?; // Propagate error
Ok(contents)
}
```
## Summary
- `panic!` for unrecoverable errors
- `unwrap()` returns value or panics
- `expect()` returns value or panics with message
- `Option` for values that may not exist (Some/None)
- `Result` for recoverable errors (Ok/Err)
- `?` operator propagates errors
- `unwrap_or()` and `unwrap_or_else()` provide defaults
- `map()` transforms Option/Result values
- Create custom error types with From trait for conversion
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →