#error #handler #fatal #stop #null #user #tiered

nightly no-std tiered-result

A different approach to error handling

1 unstable release

0.1.0 Dec 3, 2021

#10 in #fatal

MIT license

22KB
436 lines

Tiered Result

An alternate strategy for error handling.

Checkout the docs on docs.rs.


lib.rs:

This crate is an alternate strategy for error handling for libraries.

Public functions that might want to keep trying in case of error, or report multiple errors can take an additional parameter &mut dyn ErrorHandler. When they encounter an error that they can sort-of recover from, they can invoke the error handler (which calls back into the library user's code) and the error handler gets to decide if the function should keep going or stop early.

When the error handler says to return early, this is considered a fatal error and all further processing and recovery should stop, and the library should return control to the caller as quickly as possibly.

If the error handler says to continue but the error does not allow the calculation to finish, it should return null. This leads to the four variants in a TieredResult: [Ok], [Err], Null, and Fatal.

Your library code might look something like this:

use tiered_result::TieredResult;
use std::fmt::{Display, Formatter};
pub use tiered_result::{ErrorHandler, ClientResponse};
use nullable_result::NullableResult;

pub fn public_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> Option<i64> {
    match private_fn(handler) {
        TieredResult::Ok(n) => Some(i64::from(n)),
        TieredResult::Err(_) => Some(-1),
        TieredResult::Null => None,
        TieredResult::Fatal(_) => Some(-2)
    }
}

fn private_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> TieredResult<u32, Error, Fatality> {//!
    let n = another_private_fn(handler)?; // <-- this `?` bubbles up the fatal error
                                          //     leaving a nullable result behind
    let n = n?; // <-- this `?` bubbles up the null/err.
    // the previous two lines could be written as let n = another_private_fn(handler)??;

    if n == 42 {
        handler.handle_error(Error("we don't like 42".to_owned()))?;
    }
    TieredResult::Ok(n + 5)
}

fn another_private_fn(handler: &mut dyn ErrorHandler<Error, Fatality>) -> TieredResult<u32, Error, Fatality> {
    // --snip--
    # TieredResult::Ok(2)
}

// a struct to represent fatal errors, can carry additional info if you need it to
struct Fatality;

#[derive(Clone, Debug)]
struct Error(String); // any old error type

impl std::error::Error for Error {}
impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
       write!(f, "There was an error: {}", self.0)
    }
}

The calling application might look like this:

use the_lib_from_before::*;

#[derive(Default)]
struct Handler(u8);

impl ErrorHandler<Error, Fatality> for Handler {
    fn handle_error(&mut self, error: Error) -> ClientResponse<Fatality, ()> {
        if self.0 > 2 { // allow two errors before giving up
            ClientResponse::Throw(Fatality)
        } else {
            ClientResponse::Continue(())
        }
    }
}

let mut handler = Handler::default();
println!("{:?}", public_fn(&mut handler));

Dependencies

~25KB