Rust - Error Handling


There are two types of errors in rust, recoverable errors and unrecoverable errors.

Propagating Errors

To proprogate and error, return the error.

The ? operator

The ? operator can be used to immediately return an error from a function! The ? operator is only valid with functions that return Result or Option (or any type that implements Try). Example:

use std::fs::File;
use std::io;
use std::io::Read;

fn read_from_file(f_name: &str) -> Result<String, io::Error> {
    let f = File::open(f_name);

    let mut opened_file = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match opened_file.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn shortcut_read_from_file(f_name: &str) -> Result<String, io::Error> {
    let mut f = File::open(f_name)?;  // Return Err if error occurred
    let mut s = String::new();
    f.read_to_string(&mut s)?; // Return Err if error occurred
    Ok(s)
}

fn super_short_read_from_file(f_name: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

use std::fs;
fn the_shortest_read_from_file(f_name: &str) -> Result<String, io::Error> {
    fs::read_to_string(f_name)
}

fn main() {
    let f_name = String::from("file.txt");
    let s1 = read_from_file(&f_name).unwrap();
    println!("{}", s1);
}

Recoverable

Rust uses a Result<T, E> enum to catch errors. Functions that have the possibility of erroring returns the Result type. Example:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("file.txt");

    let f = match f {
        Ok(file) => file,  // Return the file
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("file.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Cannot create the file: {:?}", e),
            },
            other_error => {
                panic!("Problem opening the file: {:?}", other_error)
            }
        },
    };
}
Unwrap

unwrap can be used as a short cut if you want to panic! on error. If an Ok is returned, then unwrap will return the value inside of the OK.

let f = File::open("file.txt").unwrap();
Unwrap-Or-Else

Like unwrap but executes closure if error occured.

let f = File::open("file.txt").unwrap_or_else(|err| {
    println!("Error: {}", err);
    // Do something else
});
Expect

Similar to unwrap, expect can be used to give your own error message in the case of an error.

let f = File::open("file.txt").expect("Failed to open file.txt");
Is Error

Returns true if an error occurred:

let debug: bool = env::var("DEBUG").is_err();

Unrecoverable

panic! is how to raise an unrecoverable error.

By default, when a panic occurs, the program starts unwinding by walking up the stack and cleaning up the data for each function. Alternatively, you can abort, which ends the program without cleaning up. Aborting leads to a smaller binary.

In order to make panics abort instead of unwind in production code (to create smaller binaries), you must add the following to your Cargo.toml file:

[profile.release]
panic = 'abort'
Backtrace

Rust provides backtracing. To use do:

$ RUST_BACKTRACE=1 cargo run