impl<T, E, Ts, Es> FromIterator<Result<T, E>> for Result<Ts, Es>

Where Ts: FromIterator<T> and Es: FromIterator<E>

Sometimes I find myself with an iterator that yields results. Rust lets me collect this iterator into a Result<Vec<T>, E>, where the Err variant would hold the first encountered error, but sometimes I want to collect all errors, and not stop iteration after the first one.

By symmetry, I already expected some implementation like the one proposed to exist, but it doesn't, and I am forced to manually build those collections

compare

results.collect::<Result<Vec<_>, Vec<_>>()

to

let (oks, errs) = results.fold((Vec::new(), Vec::new()), |(mut oks, mut errs), res| {
    match res {
        Ok(t) => oks.push(t),
        Err(e) => errs.push(e),
    }
    (oks, errs)
);

if errs.is_empty() {
    Ok(oks)
} else {
    Err(errs)
}

or even

results.fold(Ok(Vec::new()), |mut acc, res| {
    match (&mut acc, res) {
        (Ok(v), Ok(t)) => v.push(t),
        (Ok(_), Err(e)) => return Err(Vec::from([e])),
        (Err(v), Err(e)) => v.push(e),
        (Err(_), Ok(_)) => (),
    }
    acc
})
1 Like

Unfortunately this conflicts with the current:

impl<A, E, V> FromIterator<Result<A, E>> for Result<V, E>
where
    V: FromIterator<A>

It overlaps when E: FromIterator<E> and this isn't even that exotic, for example String and TokenStream both satisfy this and are sometimes used as error types.

4 Likes

Oh wow, hadn't considered that, that's a bummer

Could it still work somehow? Like with specialization?

partition gets you halfway there:

let res: (Vec<_>, Vec<_>) = results.partition(Result::is_ok);

Halfway because the results will still be wrapped in Result. But itertool's partition_map and partition_result do exactly what you want.

1 Like

It would "somehow" work but:

  • it would give wrong results for error types E that implement FromIterator<E> (like String and TokenStream)
  • it would be an unsound specialization (at least with the current specialization implementation) because E could contain lifetimes

I think a middle ground could be something like the following, but I can't test right now if it conflicts with other impls:

impl<A, E, V> FromIterator<Result<A, E>> for Result<V, Vec<E>>
where
    V: FromIterator<A>

This would work assuming we don't ever implement implement FromIterator<Vec<T>> for Vec<T>, which is not that unreasonable either though.

2 Likes

They don't, I wanted to have a Result with a vec of Ok values or a vec of Err values

partition_result return a tuple with both, from there I can check if the error vec is empty, but it's still not as ergonomic

Here's a generic version which uses the short-circuiting collect up to the first error (if any), then collects the rest of the errors.

pub fn collect_errors<I, T, E, Ts, Es>(it: I) -> Result<Ts, Es>
where
    I: IntoIterator<Item = Result<T, E>>,
    Ts: FromIterator<T>,
    Es: FromIterator<E>,
{
    let mut iter = it.into_iter();
    iter.by_ref().collect::<Result<Ts, E>>().map_err(|e| {
        core::iter::once(e)
            .chain(iter.filter_map(Result::err))
            .collect()
    })
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.