🚀
8. Error Handling

Error Handling

  • Rust has a robust error handling system

Panic

  • Rust has a panic! macro to stop the program when an error occurs
fn main() {
    panic!("crash and burn");
}

Runtime Error:
thread 'main' panicked at 'crash and burn', src/main.rs:2:5

  • panic! macro can be used to generate a backtrace

Result Enum

  • Rust uses the Result enum to handle errors
use std::fs::File;
 
fn main() {
    let f = File::open("hello.txt");
 
    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}
  • match keyword is used to handle the Result enum
  • Result has two variants: Ok and Err
  • Ok variant indicates success and contains a value
  • Err variant indicates failure and contains an error message
use std::{fs::File, io::ErrorKind};
 
fn main() {
    let f = File::open("hello.txt");
 
    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => {
                panic!("Problem opening the file: {:?}", other_error)
            }
        },
    };
}
  • Nested match expressions can be used to handle different error types
  • ErrorKind enum is used to handle different error types
  • when ErrorKind is NotFound, create is used to create the file named hello.txt

Closure

  • Rust has a unwrap method to handle errors
use std::fs::File;
fn main() {
      let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}
  • unwrap_or_else method is used to handle errors

Expect

  • Rust has an expect method to handle errors
use std::fs::File;
fn main() {
    let f = File::open("hello.txt").expect("Failed to open the file");
}

? Operator

  • Rust has a ? operator to handle errors
 
use std::fs::File;
use std::io;
use std::io::Read;
 
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");
 
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
 
    let mut s = String::new();
 
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
 
fn main() {}
  • ? operator is used to handle errors
use std::fs::File;
use std::io;
use std::io::Read;
 
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
 
    let mut s = String::new();
 
    f.read_to_string(&mut s)?;
 
    Ok(s)
}
 
fn main() {}
  • ? operator can be used to handle errors in a more concise way
  • f.read_to_string(&mut s)? will return Ok(s) if successful, otherwise it will return Err(e)
use std::fs::File;
use std::io;
use std::io::Read;
 
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
 
    Ok(s)
}
 
fn main() {}
 
  • ? operator can be chained to handle errors in a more concise way
  • File::open("hello.txt")?.read_to_string(&mut s)? will return Ok(s) if successful, otherwise it will return Err(e)
use std::fs::File;
use std::io;
use std::io::Read;
 
fn main() {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
 
    Ok(s)
}

the ? operator can only be used in a function that returns Result or Option (or another type that implements FromResidual)

use std::fs::File;
use std::io;
use std::io::Read;
 
fn main() -> Result<(), io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
 
    Ok(())
}
  • ? operator can only be used in a function that returns Result or Option
  • main function returns Result with Ok variant

Panic vs Result vs Expect

  • In general the default should be using the Result enum and error propagation, prevents the program from crashing, and error propagation allows the caller to decide how to handle the error

  • panic! macro should be used when the program is in an unrecoverable state, used in example code, or when the program is in a state where it's not possible to recover

  • expect! method is used in prototype code, quick tests (when the program is in a state where it's not possible to recover)

  • unwrap! method is used in prototype code, when something is expected to be Ok but fails

Creating Custom Types for Validation

  • Rust allows creating custom types for validation
loop {
    let guess: i32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => continue,
    };
 
    if guess < 1 || guess > 100 {
        println!("The secret number will be between 1 and 100.");
        continue;
    }
 
    match guess.cmp(&secret_number) {
        // --snip--
    }
}
  • this works here, but doesn't scale well
struct Guess {
    value: i32,
}
 
impl Guess {
    fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }
 
        Guess { value }
    }
 
    fn value(&self) -> i32 {
        self.value
    }
}
  • this is a better way to handle validation

© 2024 Driptanil Datta.All rights reserved

Made with Love ❤️

Last updated on Mon Oct 20 2025