Rust Error Handling
Safe and explicit error management in Rust
⚠️ What is Rust Error Handling?
Rust handles errors explicitly using Result and Option types instead of exceptions. This approach makes errors visible in function signatures, forcing developers to handle them properly and preventing crashes.
// Function that might fail
fn divide(a: f64, b: f64) -> Result {
if b == 0.0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}
Error Handling Types
Result<T, E>
For operations that can fail
fn parse_number(s: &str) -> Result {
s.parse()
}
Option<T>
For values that might not exist
fn find_user(id: u32) -> Option {
if id == 1 { Some("Alice".to_string()) }
else { None }
}
panic!
For unrecoverable errors
fn critical_error() {
panic!("Something went very wrong!");
}
Custom Errors
Your own error types
#[derive(Debug)]
enum MyError {
NotFound,
InvalidInput,
}
🔹 Working with Result
Result is used for operations that can succeed or fail:
use std::fs::File;
use std::io::Read;
fn read_file_content(filename: &str) -> Result {
let mut file = File::open(filename)?; // ? operator for error propagation
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file_content("example.txt") {
Ok(content) => println!("File content: {}", content),
Err(error) => println!("Error reading file: {}", error),
}
}
Output (if file exists):
File content: Hello, world!
🔹 Working with Option
Option represents values that might not exist:
fn find_word(text: &str, word: &str) -> Option {
text.find(word)
}
fn main() {
let text = "Hello, Rust programming!";
// Using match
match find_word(text, "Rust") {
Some(index) => println!("Found 'Rust' at index: {}", index),
None => println!("'Rust' not found"),
}
// Using if let
if let Some(index) = find_word(text, "Python") {
println!("Found 'Python' at index: {}", index);
} else {
println!("'Python' not found");
}
}
Output:
Found 'Rust' at index: 7
'Python' not found
🔹 The ? Operator
Simplify error propagation with the ? operator:
🔸 Without ? operator
fn parse_and_double(s: &str) -> Result {
match s.parse::() {
Ok(n) => Ok(n * 2),
Err(e) => Err(e),
}
}
🔸 With ? operator
fn parse_and_double(s: &str) -> Result {
let n = s.parse::()?; // Automatically returns error if parsing fails
Ok(n * 2)
}
fn main() {
match parse_and_double("21") {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
Output:
Result: 42
🔹 Custom Error Types
Create your own error types for better error handling:
#[derive(Debug)]
enum CalculatorError {
DivisionByZero,
InvalidOperation,
Overflow,
}
impl std::fmt::Display for CalculatorError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CalculatorError::DivisionByZero => write!(f, "Cannot divide by zero"),
CalculatorError::InvalidOperation => write!(f, "Invalid operation"),
CalculatorError::Overflow => write!(f, "Number overflow"),
}
}
}
impl std::error::Error for CalculatorError {}
fn safe_divide(a: f64, b: f64) -> Result {
if b == 0.0 {
Err(CalculatorError::DivisionByZero)
} else {
Ok(a / b)
}
}
fn main() {
match safe_divide(10.0, 0.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
Output:
Error: Cannot divide by zero
🔹 Useful Methods
Common methods for working with Result and Option:
fn main() {
let result: Result = Ok(42);
let option: Option = Some(10);
// unwrap() - panics if error/None
let value = result.unwrap();
println!("Unwrapped: {}", value);
// unwrap_or() - provides default value
let default_value = option.unwrap_or(0);
println!("With default: {}", default_value);
// map() - transform the success value
let doubled = result.map(|x| x * 2);
println!("Doubled: {:?}", doubled);
// and_then() - chain operations
let chained = result.and_then(|x| Ok(x + 10));
println!("Chained: {:?}", chained);
}
Output:
Unwrapped: 42
With default: 10
Doubled: Ok(84)
Chained: Ok(52)