Rust Error Handling

Handling Errors Using Result and Option Types

Result Type:

The Result type is used by functions that can have an error returning it. It is an enum with two variants:It is an enum with two variants:

  • Ok(T): Indicates that the operation was successful, containing the result.
  • Err(E): Indicates that an error occurred, containing the error.

Example:

                    
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}
                    
                  

Option Type:

The Option type is used for values that may or may not be present. It is an enum with two variants:

  1. Some(T): Indicates that a value is present.
  2. None: Indicates that no value is present.

Example:

                    
fn find_value(vec: Vec<i32>, target: i32) -> Option<usize> {
    for (index, &value) in vec.iter().enumerate() {
        if value == target {
            return Some(index);
        }
    }
    None
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    match find_value(numbers, 3) {
        Some(index) => println!("Found at index: {}", index),
        None => println!("Not found"),
    }
}
                    
                  

Propagating Errors with the ? Operator

The ? operator is a shorthand for propagating errors.

If the result equals Ok, we use the value; if the result does not equalOk, we return the error from the current funciton.

Example:

                    
use std::fs::File;
use std::io::{self, Read};

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?; // Propagates error if File::open fails
    let mut contents = String::new();
    file.read_to_string(&mut contents)?; // Propagates error if read_to_string fails
    Ok(contents)
}

fn main() {
    match read_file("hello.txt") {
        Ok(contents) => println!("File contents: {}", contents),
        Err(e) => println!("Error reading file: {}", e),
    }
}
                    
                  

Custom Error Types

Creating custom error types can help in handling domain-specific errors more effectively.

You can define your own error types and implement the std::fmt::Debug, std::fmt::Display, and std::error::Error traits.

Example:

                    
use std::fmt;

#[derive(Debug)]
enum MyError {
    NotFound,
    InvalidInput,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MyError::NotFound => write!(f, "Item not found"),
            MyError::InvalidInput => write!(f, "Invalid input provided"),
        }
    }
}

impl std::error::Error for MyError {}

fn find_item(vec: Vec<i32>, target: i32) -> Result<i32, MyError> {
    for &item in vec.iter() {
        if item == target {
            return Ok(item);
        }
    }
    Err(MyError::NotFound)
}

fn main() {
    match find_item(vec![1, 2, 3, 4, 5], 6) {
        Ok(item) => println!("Found item: {}", item),
        Err(e) => println!("Error: {}", e),
    }
}
                    
                  

Rust error handling is done very effectively with powerful and flexible facilities to aid safety purpose for the programming code.