Rust errors: Difference between revisions

From wikinotes
No edit summary
Line 100: Line 100:
}}
}}
</blockquote><!-- Result -->
</blockquote><!-- Result -->
= std::error::Error =
<blockquote>
The standard library predominantly uses <code>std::error::Error</code> concretions as <code>Err</code> within Result objects.<br>
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
<syntaxhighlight lang="rust">
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.."),
        }
    }
}
</syntaxhighlight>
</blockquote><!-- std::error::Error -->

Revision as of 03:14, 9 February 2023

Rust has two primary methods of handling errors.

  • panic!() halts/exits the program
  • Result types are for handle-able errors

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 the various cases


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

Panic if not successful


// panic if error, otherwise return value
let result = is_one(2)
    .unwrap()

// panic with message if error, otherwise return value
let result = is_one(2)
    .expect("an error occurred!")  // <-- panic message

Propagate the error if not successful

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")
}


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.."),
        }
    }
}