Error Handling in Rust
Rust uses the Result
and Option
types for error handling, ensuring safety and explicitness.
Using Result
The Result
type is used for functions that can return an error.
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err("Division by zero".to_string());
}
Ok(a / b)
}
fn main() {
match divide(10.0, 0.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
Using Option
The Option
type is used for functions that may or may not return a value.
fn find_index(arr: &[i32], target: i32) -> Option<usize> {
for (i, &item) in arr.iter().enumerate() {
if item == target {
return Some(i);
}
}
None
}
fn main() {
let arr = [1, 2, 3, 4, 5];
match find_index(&arr, 3) {
Some(index) => println!("Found at index: {}", index),
None => println!("Not found"),
}
}
Custom Error Types
You can define custom error types by implementing the std::error::Error
trait.
use std::fmt;
#[derive(Debug)]
struct MyError {
message: String,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for MyError {}
fn my_function() -> Result<(), MyError> {
Err(MyError {
message: "Something went wrong".to_string(),
})
}
fn main() {
if let Err(e) = my_function() {
println!("Error: {}", e);
}
}
Combining Errors
Use the ?
operator to propagate errors and combine them using libraries like anyhow
or thiserror
.
use anyhow::{Context, Result};
fn read_file() -> Result<String> {
let content = std::fs::read_to_string("example.txt")
.context("Failed to read file")?;
Ok(content)
}
fn main() {
if let Err(e) = read_file() {
println!("Error: {}", e);
}
}
Error Handling with unwrap
and expect
Use unwrap
or expect
for quick prototyping, but avoid them in production code.
fn main() {
let result: Result<i32, &str> = Ok(42);
let value = result.unwrap(); // Panics if Err
println!("Value: {}", value);
let result: Result<i32, &str> = Err("Error");
let value = result.expect("Failed to get value"); // Panics with custom message
}
Best Practices
- Use
Result
andOption
: Always prefer these types for error handling. - Avoid
unwrap
andexpect
: Use them only for prototyping or unrecoverable errors. - Custom Error Types: Define custom error types for better error handling and reporting.
- Propagate Errors: Use the
?
operator to propagate errors up the call stack. - Combine Errors: Use libraries like
anyhow
orthiserror
for combining and managing errors.
Error Handling in Real-World Scenarios
In real-world applications, error handling often involves logging, retrying, and user-friendly error messages.
use std::fs::File;
use std::io::{self, Read};
fn read_config() -> Result<String, io::Error> {
let mut file = File::open("config.toml")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
match read_config() {
Ok(config) => println!("Config: {}", config),
Err(e) => eprintln!("Failed to read config: {}", e),
}
}
Next: Concurrency in Rust