rust error handling

Unrecoverable errors with panic!

// print a failure message, unwind and clean up the stack, and then quit
fn main() {
    panic!("crash and burn");
}

By default, Rust walks back up the stack and cleans up the data from each function it encounters. Walking back and cleaning up is a lot of work.

You can immediately abort and let the OS clean up by adding the following in the Cargo.toml:

# example of adding abort on panic in release mode
[profile.release]
panic = 'abort'

We can set the RUST_BACKTRACE environment variable to get a backtrace of exactly what happened to cause the error:

$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483
   1: core::panicking::panic_fmt
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:85
   2: core::panicking::panic_bounds_check
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:62
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:255
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:15
   5: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/alloc/src/vec.rs:1982
   6: panic::main
             at ./src/main.rs:4
   7: core::ops::function::FnOnce::call_once
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Recoverable errors with Result

// The `Result` enum is defined as having two variants: `Ok` and `Err`
enum Result<T, E> {
    Ok(T),
    Err(E),
}
 
 
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),
    };
}

Matching on different errors

use std::fs::File;
use std::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)
            }
        },
    };
}
// same as above but more concise
use std::fs::File;
use std::io::ErrorKind;
 
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);
        }
    });
}

Shortcuts for panic on Error: unwrap and expect

use std::fs::File;
 
fn main() {
    // will call panic! if Err variant
    let f = File::open("hello.txt").unwrap();
}
use std::fs::File;
 
fn main() {
    // same as above, but provides a good error message
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

Propagating Errors

use std::fs::File;
use std::io::{self, Read};
 
// return the error to the calling code so it gives more control to the calling code
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),
    }
}
use std::fs::File;
use std::io;
use std::io::Read;
 
fn read_username_from_file() -> Result<String, io::Error> {
    // shortcut for propagating Errors with ? operator
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
 
 
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
 
    // chaining method calls after the ? operator
    File::open("hello.txt")?.read_to_string(&mut s)?;
 
    Ok(s)
}
use std::error::Error;
use std::fs::File;
 
// another return type `main` can have is `Result<(), E>`
// `Box<dyn Error>` is a trait object == any kind of error
fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;
 
    Ok(())
}