Skip to content

Error Handling

Updated
17 Sept 2026
3 min read
0 sections
Page 17 of 18
Rust
  • Rust has a robust error handling system

Panic

  • Rust has a panic! macro to stop the program when an error occurs
rust
fn main() {
    panic!("crash and burn");
}
Out · error
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
rust
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{:rust} keyword is used to handle the Result{:rust} enum
  • Result{:rust} has two variants: Ok{:rust} and Err{:rust}
  • Ok{:rust} variant indicates success and contains a value
  • Err{:rust} variant indicates failure and contains an error message
rust
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{:rust} expressions can be used to handle different error types
  • ErrorKind{:rust} enum is used to handle different error types
  • when ErrorKind{:rust} is NotFound{:rust}, create{:rust} is used to create the file named hello.txt

Closure

  • Rust has a unwrap method to handle errors
rust
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{:rust} method is used to handle errors

Expect

  • Rust has an expect method to handle errors
rust
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
rust

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
rust
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() {}
  • ?{:rust} operator can be used to handle errors in a more concise way
  • f.read_to_string(&mut s)?{:rust} will return Ok(s){:rust} if successful, otherwise it will return Err(e){:rust}
rust
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() {}
  • ?{:rust} operator can be chained to handle errors in a more concise way
  • File::open("hello.txt")?.read_to_string(&mut s)?{:rust} will return Ok(s){:rust} if successful, otherwise it will return Err(e){:rust}
rust
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)
}
Out · error
the ? operator can only be used in a function that returns Result or
Option (or another type that implements FromResidual)
rust
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(())
}
  • ?{:rust} operator can only be used in a function that returns Result{:rust} or Option{:rust}
  • main{:rust} function returns Result{:rust} with Ok{:rust} variant

Panic vs Result vs Expect

  • In general the default should be using the Result{:rust} enum and error propagation, prevents the program from crashing, and error propagation allows the caller to decide how to handle the error
  • panic!{:rust} 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!{:rust} method is used in prototype code, quick tests (when the program is in a state where it's not possible to recover)
  • unwrap!{:rust} method is used in prototype code, when something is expected to be Ok{:rust} but fails

Creating Custom Types for Validation

  • Rust allows creating custom types for validation
rust
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
rust
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