Recoverable Errors with Result
The Result<T, E>
enum handles recoverable errors:
enum Result<T, E> { Ok(T), Err(E), }
Basic Usage
use std::fs::File; fn main() { let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => panic!("Problem opening file: {error:?}"), }; }
Handling Different Error Types
use std::fs::File;
use std::io::ErrorKind;
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating file: {error:?}");
})
} else {
panic!("Problem opening file: {error:?}");
}
});
Shortcuts: unwrap
and expect
// unwrap - panic with default message on error let f = File::open("hello.txt").unwrap(); // expect - panic with custom message let f = File::open("hello.txt") .expect("hello.txt should be included in this project");
Use expect
over unwrap
in production code for better debugging.
Error Propagation
Manual propagation:
fn read_username_from_file() -> Result<String, io::Error> { let username_file_result = File::open("hello.txt"); let mut username_file = match username_file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(e) => Err(e), } }
The ?
Operator
The ?
operator simplifies error propagation:
fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } // Even more concise with method chaining fn read_username_from_file() -> Result<String, io::Error> { let mut username = String::new(); File::open("hello.txt")?.read_to_string(&mut username)?; Ok(username) } // Using standard library convenience function fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") }
The ?
operator:
- If
Ok(value)
- returnsvalue
- If
Err(e)
- returns early withErr(e)
converted viaFrom
trait
?
Operator Constraints
?
can only be used in functions returning compatible types:
fn main() {
let greeting_file = File::open("hello.txt")?; // Error: main returns ()
}
Fix by changing return type:
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
Using ?
with Option
fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() }
Common Patterns
Convert between Result and Option:
// Result to Option let opt: Option<String> = File::open("hello.txt").ok(); // Option to Result let result: Result<String, &str> = some_option.ok_or("value was None");
Chaining operations:
let processed = input .parse::<i32>()? .checked_mul(2) .ok_or("overflow")?;
Early returns in main:
fn main() -> Result<(), Box<dyn Error>> { let config = load_config()?; let data = fetch_data(&config)?; process_data(data)?; Ok(()) }
The ?
operator makes error handling ergonomic while maintaining explicit control flow, similar to how async/await improves Promise handling in TypeScript.