Rust errors

From wikinotes

Rust has two primary methods of handling errors.

  • panic!() halts/exits the program
  • Result types are for handle-able errors (which frequently pass std::error::Error objects)

panic

  • intended for halting application, not control flow
  • have backtraces
panic!("tried to X but couldn't Y")

// convert panic to result
// (not intended for native rust code)
let result = panic::catch_unwind(|| {
    panic!("oh no!");
});

Result

The result type is an enum, whose options Ok, Err have been merged into the global scope through prelude.
Err can be any type, but the standard library predominantly uses std::error::Error concretions.

Given this Result producing code:

use std::error::Error;
use std::fmt;

// A custom std::error::Error impl
#[derive(Debug)]
struct MyError {}
impl Error for MyError{}
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "i borked because...")
    }
}

// a result type
fn is_one(i: isize) -> Result<String, MyError> {
    if i == 1 {
        Ok("success".to_string())

    } else {
        Err(MyError{})
    }
}

match Ok/Err (optionally break/return/panic on Err)


let result = match is_one(2) {
    Ok(x) => format!("horay: {}", x),
    Err(_) => panic!("an error occurred")
};
println!("{}", result);

Propagate if Err()

It's also fairly common that you want to delegate handling the error to the caller.
There is also syntactic sugar for this.

The ? operator returns with the Err if it errored.
You'll only need to provide the Ok() value.
(note you can also pass an Option and receive an Option in the same manner)

fn print_if_result_ok(i: isize) -> Result<String, MyError> {
    let result = is_one(i)?                 // return Err() if err
    println!("success value: {}", result);  // unwraps result, and you know it is Ok() now
    Ok("success")
}


Common Result Methods


Confirm status

.is_ok()   // true if is Ok-val
.is_err()  // true if is Err-val

Expected Result, or Panic w/ Message

.expect("error because..")        // panic with this mssage if Err(), otherwise returns Ok-val
.expect_err("error because..")    // panic with this message if Ok(), otherwise return Err-val

Unwrap (to raw val)

// panic variations
.unwrap()                         // Ok-val or panic!
.unwrap_err()                     // Err-val or panic!

// non-panic variations
.into_ok_or_err()                 // return Ok-val if Ok(), Err-val if Err()
.unwrap_or(456)                   // (eager) Ok-val, w/ fallback on default
.unwrap_or_else(|_| 1 + 1)        // (lazy)  Ok-val, w/ fallback on default, calculated in closure
.unwrap_or_default()              // Ok-val, w/ fallback on Ok-val-type's default

Map (transform Ok/Err vals)

.map(|v| v.to_string())           // Mutate the Ok-vals, preserve Err-vals, return Result
.map_err(|e| e.as_str())          // Mutate the Err-vals, preserve Ok-vals, return Result

Or (try changing Err to Ok)

// (eager) If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or(|r| Ok(r.unrap() * 2))

// (lazy)  If Err-val, use closure to try changing it to Ok-val
// (also accepts Err-val), return Result
.or_else(|r| Ok(r.unwrap() * 2))

Convert to Option

.ok()  // convert to Option (Ok->Some, Err->None)
.err() // convert to Option (Ok->None, Err->Some)

Misc

.iter()                           // (iterate over contained value? if ok-val is array?)
.contains(2)                      // return true if item is Ok(), and Ok-val matches


std::error::Error

The standard library predominantly uses std::error::Error concretions as Err within Result objects.
It's particularly useful to implement errors as enums, to enumerate the various errors it can handle.

Error implementors must:

  • be derived from Debug
  • implement Display
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub enum MyError {
    FileNotAccessible,
    FileIsLocked,
}

impl Error for MyError{}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::FileNotAccessible => write!(f, "file not accessible.."),
            MyError::FileIsLocked => write!(f, "file is locked.."),
        }
    }
}