← Rust EnglishChapter 09 of 13

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 →