One of my biggest papercuts with Rust is that it's kind of awkward to handle intermediate Results in an iterator. Usually, the choices are to either create intermediate collections with collect(), or to handle the Results at every subsequent step of the iterator chain. A couple years ago, I posted a proposal here, but it needed some refining. I came back to this problem over the past weekend, and had a flash of inspiration.
The idea is to add this method to Iterator:
/// Continue the iterator stream on `Ok` values, stopping at the first `Err` value.
/// The supplied function `f` takes an iterator over `Self::Item`.
/// If all values of `Self` are `Ok`, `continue_with` returns the result of this function.
/// If any `Err`s are encountered, it returns the first error and does not evaluate further.
fn continue_with<F, U>(self, f: F) -> Result<U, Self::Error>
where F: FnOnce(ContinueWith<Self, Self::Error>) -> U;
A proof-of-concept is on the playground here, along with a example of using this to handle several intermediate Results.
What do you think? Off the top of my head, I have a couple thoughts for improvements, if anyone has any suggestions:
I'm not sure on the name.
It would be nice not to expose the ContinueWith struct, but I'm not sure how to do so.
Are you aware of the fact that Result implements the FromIterator trait (which is used by collect())? Why doesn't that cover what you're trying to do here?
Sometimes you want to treat the Ok values from the iterator as an iterator unto themselves, and not just as individual values to be mapped over -- that's where this comes in handy.
A process_results function also already exists internally in std used for implementing the abovementioned capability to collect() an iterator of Results.
Ah, I was not aware of process_results (or try_process)! This is sort of an inline version of that, then. If there's not much interest in it for Iterator, I can see if Itertools would be interested.
That makes sense -- my thinking was that iterator adaptors usually expose their structs in the return type, whereas this type was in the argument, but I suppose it's the same either way from a user perspective.
I personally think the former is easier to read and write, because when you get to the part of the iterator pipeline that produces a Result, you don't have to go back to the beginning of the iterator pipeline to insert a call to process_results() and keep track of the arguments to that.
One think I'll add here is that this is, to some extent, fundamental to laziness.
The magic thing that would make everything easier would be a function for impl Iterator<Item = Result<T, E>> -> Result<impl Iterator<Item = T>, E>. But that cannot be implemented lazily, because it would need to look at all the items to find out whether it needs to return Ok or Err.