Non-short-circuiting version of iterator.find()

I want to execute a bunch of functions that all return results. I want to return the first error that occurred in the executions, but I always want to execute all functions, even if an error occurred somewhere. I initially thought of using

iterator.map(|f| f.execute).find(Result::is_err).unwrap_or(Ok(()))

but find() is short-circuiting, meaning it won't continue executing after finding the first error. What is the best way to implement my requirements? Is there some non-shortcircuiting version of find()

Does it have to be the first error or can it be any error? I'm asking because iterators try to be short-circuiting whenever possible. So find, collect and filter(...).next() are all short-circuiting and off the table. What isn't is filter(Result::is_err).last(), so if you are comfortable with returning the last error, you could do something like:

iterator.map(|f| f.execute).filter(Result::is_err).last().unwrap_or(Ok(()))

Playground.

2 Likes

Wouldn't the issue there be that there might be some Ok values after the last error that wouldn't be executed with that solution?

No, because you have to execute them to know that they are Oks. last can't short circuit; it has to keep calling next on the inner iterator until it returns None.

4 Likes

You can do it with fold:

iterator.fold(Ok(()), |result, f| {
    result.and(f.execute())
}

Like map, fold never short-circuits.

Result::and selects either an error from the result accumulator (the first error seen) or the result from the execute function (either Ok or Err).

Because the argument to and is executed before and sees that result is already an Err, f.execute() will be run on each item, even when result is an Err.

14 Likes

Wow, this is super clean! Great solution!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.