CodeToLive

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

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