Crate first_err

source ·
Expand description

first-err

Find first Err in Iterator<Result<T, E>> and allow to iterating continuously.

This crate is specifically designed to replace the following pattern without allocation:

// iter: impl Iterator<Result<T, E>>
iter.collect::<Result<Vec<T>, E>>().map(|vec| vec.into_iter().foo() );

Features

  • Find first Err in Iterator<Result<T, E>> and allow to iterating continuously.
  • Speed: rough on par with hand write loop, use lazy evaluation and without alloc.
  • Minimized: no_std, no alloc, no dependency.

Getting Started

This crate help you to take first Err in a Result and keep iterating without pay for allocation, here is a sample:

use first_err::FirstErr;

// Everything is Ok.
let ans = [Ok::<u8, u8>(0), Ok(1), Ok(2)]
    .into_iter()
    .first_err_or_else(|iter| iter.sum::<u8>());
assert_eq!(ans, Ok(3));

// Contains some `Err` values.
let ans = [Ok::<u8, u8>(0), Err(1), Err(2)]
    .into_iter()
    .first_err_or_else(|iter| iter.sum::<u8>());
assert_eq!(ans, Err(1));

See FirstErr::first_err_or_else() for more detail.

Why

In Rust, I always encountered a kind of pattern which is I need to do something on all items within an iterator, and should also cancel as soon as possible if any error is found in current working layer. But, if no error found, the iterator should able to run continuously and allow me to do further transform.

The pattern typically looks as follows:

let array: [Result<u8, u8>; 3] = [Ok(0), Err(1), Err(2)];

fn fallible_sum(iter: impl IntoIterator<Item = Result<u8, u8>>) -> Result<u8, u8> {
    let sum = iter
        .into_iter()
        .collect::<Result<Vec<_>, _>>()?    // early return (and a vec alloc in here)
        .into_iter()                        // continue iterate next layer ...
        .sum();

    Ok(sum)
}

let ans = fallible_sum(array);
assert_eq!(ans, Err(1));

In theory, this allocation is not necessary. We can just write that code as an old good loop:

let array: [Result<u8, u8>; 3] = [Ok(0), Err(1), Err(2)];

fn fallible_sum(iter: impl IntoIterator<Item = Result<u8, u8>>) -> Result<u8, u8> {
    let mut sum = 0;
    for res in iter {
        let val = res?;                     // early return, no alloc
        sum += val;
    }

    Ok(sum)
}

let ans = fallible_sum(array);
assert_eq!(ans, Err(1))

Using a loop is not bad at all. But for some situation, I would like to keep iterator chainable as much as possible. This crate offers another approach to achieve it.

And even further, sometime life may not simple like previous example. consider is one:

// The second layer `Result` is usually created by further transform after the first layer
// `Result` be processed. But for the sake of simplicity, we've just use pre-defined values.
let array: [Result<Result<u8, u8>, u8>; 3] = [Ok(Ok(0)), Ok(Err(1)), Err(2)];

fn fallible_sum(
    iter: impl IntoIterator<Item = Result<Result<u8, u8>, u8>>
) -> Result<u8, u8> {
    // take "first `Err`" layer by layer, or the sum value.
    let sum = iter
        .into_iter()
        .collect::<Result<Vec<Result<u8, u8>>, u8>>()?
        .into_iter()
        .collect::<Result<Vec<u8>, u8>>()?
        .into_iter()
        .sum();

    Ok(sum)
}

let ans = fallible_sum(array);
assert_eq!(ans, Err(2));

Above logic may little hard to write as a loop without alloc. But this crate can do it for you:

let array: [Result<Result<u8, u8>, u8>; 3] = [Ok(Ok(0)), Ok(Err(1)), Err(2)];

fn fallible_sum(
    iter: impl IntoIterator<Item = Result<Result<u8, u8>, u8>>
) -> Result<u8, u8> {
    iter
        .into_iter()
        .first_err_or_else(|iter1| { // iter1 = impl Iterator<Item = Result<u8, u8>>
            iter1.first_err_or_else(|iter2| { // iter2 = impl Iterator<Item = u8>
                iter2.sum::<u8>()
            })
        })
        .and_then(|res_res| res_res)
}

let ans = fallible_sum(array);
assert_eq!(ans, Err(2));

Benchmark

This crate’s performance character is designed for rough on par with hand write loop. But compiler may do some better optimization for one or another in difference situations.

If you want do benchmark by yourself, use follows command:

cargo bench --bench benchmark -- --output-format bencher

And don’t forget check which code I actual bench in benches folder.

Structs

  • Iterator can take first error from inner iterator.

Traits

  • This trait provides first_err_or_else() method on all Iterator<Item = Result<T, E>>.