Rust :: Error Handling

Error Handling

Termination

// panic! stops the program right away
panic!("string literal");

// std::process::exit
fn run_app() -> Result<(), ()> {
    // Application logic here
    Ok(())
}
fn main() {
    std::process::exit(match run_app() {
        Ok(_) => 0,
        Err(err) => {
            eprintln!("error: {:?}", err);
            1
        }
    });
}

// exit code is 0 on Linux; 256 on Windows
use std::process;
process::exit(0x0100);

// std::process::abort
// similar when Cargo.toml set `panic="abort"`
// `panic!` still call panic hook, `abort` will not
std::process::abort();

Option »

// Option is a type of enum in standard lib
// <T> generic type parameter
enum Option<T> {
    None,
    Some(T),
}

// need to specity type for None
let abs: Option<i32> = None;
let some = Some(5);
let some_string = Some("string");

// matching with Option<T>
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

// Option impl `map` methods and `map_or`
pub fn map<U, F>(self, f: F) -> Option<U>
where
    F: FnOnce(T) -> U,

let maybe_some_string = Some(String::from("Hello, World!"));
// `Option::map` takes self *by value*, consuming `maybe_some_string`
let maybe_some_len = maybe_some_string.map(|s| s.len());
assert_eq!(maybe_some_len, Some(13));

// query variant
option.is_some()    // return bool
option.is_none()

// transform contained value to Result
option.ok_or(err)  // Some(v) to Ok(v), and None to Err(err)
option.ok_or_else(Fn) // None to value of Err using FnOnce

// methods on Some variant
.filter()   // return Some(t) or None
.flatten()  // converts from Option<Option<T>> to Option<T>
.map()      // convert Option<T> to Option<U> with fn, None unchanged
.map_or(default: U, f: FnOnce(T) -> U)  // provide default value
.map_or_else(D, F)  // use Fn D if None, or use Fn F

// create Some
.zip(Option<U>) -> Option<(T,U)>    // None if U is None

// use collect to turn a slice of Options to Option of a slice
// return None if any of the item is None

Result »

enum Result<T,E> {
    Ok(T),
    Err(E),
}

// example
match result {
    Ok(v) => println!("working with version: {:?}", v),
    Err(e) => println!("error parsing header: {:?}", e),
}

// Result also has `map`, `map_or` methods
pub fn map<U, F>(self, op: F) -> Result<U, E>
where
    F: FnOnce(T) -> U, 

fn name() -> Cow<'static, str> {
    std::env::var("USER")
        .map(|v| v.into())
        .unwrap_or(Cow::Borrowed("whoever you are"))
}

// is_ok() and is_err(), return bool
// this method does not consume `self`
if some_result.is_ok() { /* do something */ }

// return ref and not to consume result
result.as_ref() -> Result<&T, &E>
result.as_mut() -> Result<&mut T, &mut E>

// consumes `self`
.ok()   //convert from Result<T,E> to Option<T>
.err()  //convert to Option<E>, either None or Some(E)
let x: Result = Ok(2);
x.ok();     // Some(2)

.map(Fn) -> Result<U, E>    // from Result<T, E>
.map_err(Fn) -> Result<T, F>    // Ok unchanged
.map_or(U, Fn) -> U // default U, or use Fn
.map_or_else(Fn1, Fn2) -> U  // Fn1 for E, Fn2 for Ok 

// use collect to turn slice of a Result to Result<slice, E>
// a string of numbers that may contain letters
"12345028a0329b".chars().map(|c| c.to_digit(10).ok_or(SomeError))
                .collect::<Result<Vec<_>, _>>()?

// Result Type ALiases
type Result<T> = result::Result<T, Error>;
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T, GenericError>;

Error Handling

unwrap

// unwrap(): can be used for Option and Result
// return T if Ok, and pacnic! if Err
use std::fs::File;
File::open("hello.txt").unwrap();

// upwrap_or(): return Ok value or a default
.unwrap_or(T) -> T
.unwrap_or_else(FnOnce(E)->T) -> T  // return Ok value or computers from Fn
.unwrap_or_default() -> T   // T must impl Default

// unwrap_err(): panics if the value is Ok, and returns the value of Err
error.unwrap_err();

// expect(): add error message
File::open("hello.text").expect("Failed to open hello.txt");

? operator

use std::num::ParseIntError;
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
    let first_number = first_number_str.parse::<i32>()?;
    let second_number = second_number_str.parse::<i32>()?;
    Ok(first_number * second_number)
}

// another example
use std::fs::File
use std::io;
fn open_file() -> Result<File, io::Error> {
    let f = File::open("hello.txt")?;
    Ok(f)
}

// for main(), need to return Result<T, E>
// use Box<dyn error::Error> if there is multiple E
use std::error;
use std::fs::File;
fn main -> Result<(), Box<dyn error::Error>> {
    let f = File::open("hello.txt")?;
    Ok(())
}

Custom Error Type

From Trait

use std::{fs, io, num};

enum CustomError {
    IoError(io::Error),
    ParseError(num::ParseIntError),
}

impl From<io::Error> for CustomError {
    fn from(e: io::Error) -> Self {
        CustomError::IoError(e)
    }
}

impl From<num::ParseIntError> for CustomError {
    fn from(e: num::ParseIntError> -> Self {
        CustomError::ParseError(e)
    }
}
fn open_file(f: &str) -> Result<i32, CustomError> {
    let mut c = fs::read_to_string(&f)?;
    let n: i32 = c.trim().parse()?;
    Ok(n)
} 

Error Trait

// usually not necessary to implement any method
// TODO: when `Error` trait is needed
impl std::io::Error for CustomErrorType {}

// impl source method to reveal lower-level error
impl Error for CustomError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        if let CustomError::ParseInt(e) = &self {
            return Some(e);
        }
        None
    }
}
// unwrap since it's an Option
let err = error.source().unwrap();

// another way is impl Display
impl Display for CustomError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ParseInt(e) => write!(f, "error parsing INT {}", e),
            _ => write!(f, "other types of error"),
        }
    }
}

// error downcast
error.downcast_ref::<ErrorType>()
println!()  // both {} and {:?}

err.to_string()
err.source()    // impl `source`


// example to print all available information
use std::error::Error;
use std::io::{Write, stderr};

/// Dump an error message to `stderr`.
///
/// If another error happens while building the error message or
/// writing to `stderr`, it is ignored.
fn print_error(mut err: &dyn Error) {
    // use `writeln!` instead of `eprintln!` because `eprintln!` panics if error occurs
    let _ = writeln!(stderr(), "error: {}", err);
    while let Some(source) = err.source() {
        let _ = writeln!(stderr(), "caused by: {}", source);
        err = source;
    }
}
fn main() {
    if let Err(err) = run() {
        print_error(&err);
        std::process::exit(1);
    }
}