#error #parser #value #input #outcome #operation #gather

nightly ocm

Outcome type for a value and its errors

1 unstable release

0.1.0 Aug 3, 2024

#2921 in Rust patterns

MIT license

34KB
275 lines

ocm

The ocm crate provides Outcome<T, E>, an ergonomic type to model operations which can produce several errors while always returning some value.

This is useful for operations like parsing, where you'd like to gather as many errors as possible before failing.

use ocm::{Outcome, ErrorCollector};

// Some operation which always returns a value, but may produce errors while doing so
pub fn sum_ints<'a>(input: &[&'a str]) -> Outcome<u32, String> {
    Outcome::build(|errors| {
        let mut sum = 0;

        for item in input {
            match item.parse::<u32>() {
                Ok(num) => sum += num,
                Err(_) => errors.push_error(format!("not a number: {item}")),
            }
        }

        sum
    })
}

let outcome = sum_ints(&["123", "456", "abc", "789", "def"]);

// `Outcome::finalize` breaks the outcome into:
//   - The value
//   - An `ErrorSentinel`, which dynamically ensures that errors are handled
let (value, errors) = outcome.finalize();
println!("Sum value: {value}");
if errors.any() {
    println!("Errors:");
    for err in errors.into_errors_iter() {
        println!("  - {err}");
    }
}

Just bundling a value and errors together risks that the errors are accidentally ignored. We'd like to be sure that the errors are handled appropriately.

Unfortunately, Rust lacks the linear type system which would be required to do this statically, so instead it is done at runtime, using panics to signal that errors were dropped without being handled:

use ocm::Outcome;

let outcome = Outcome::new_with_errors(42, vec!["error 1", "error 2"]);
let (value, errors) = outcome.finalize();

println!("Value: {value}");
// Whoops! `errors` was never handled - this will panic.

No runtime deps