Add the associated methods try_any and try_all to the Iterator trait, which work similar to any and all, but accept Result and Option as closure return type.
Motivation
In some cases where one would like to use any/all, the actual condition to check is fallible. For example, let's say we have a simple key-value-store like a HashMap. The keys are IDs to the objects. We further have a list of IDs we want to check some condition on. We would like to use all on an iterator over the list to ensure all objects match our condition. However, access to elements is fallable and returns Options. In other cases, the checks might involve more complex validation on the objects. The validation method could involve access to external parts and fail. Therefore, it returns a Result<bool, Error>.
The implementor needs to return a bool for every element in all. They could use .try_fold(true, |state, item| Ok(state && check(item)?)), but this is less readable and does not short-circuit as all does. If they would do .all(|item| check(item).unwrap_or(false)), the error would be discarded and lost/ignored. To fully comply with expectations, there is only two possibilites (I see):
- a
forloop is required to iterate over the elements and stop early on errors or invalid items - collecting the elements in the iterator to a collected
Result, then callingallon the inner collection
Both solutions are not very elegant. The try_all method would work as .try_all(check), returning Result<bool, Error> as well. It should behave similar to all, except working with Try<bool> output, just as try_fold behaves in comparison to fold.
Everything applies similarly to try_any.
Guide-level explanation
These additions of try_all and try_any to the core library of Rust allow the use of fallible validators in iterators.
When every element in an iterator needs to be validated, it could be done like this:
It works similarly with Options, e.g. the mentioned key-value-store:
Reference-level explanation
The addition try_all behaves to all like try_fold behaves to fold. The similar applies to try_any. They both should react as their simple counter-part: short-circuit on elements that already determine the overall result. However two things are different:
- They return the same type as the given function/closure does, so
Option<bool>orResult<bool, E>. - They also short-circuit on try-fails.
Exemplary implementation
Inside the Iterator trait:
Drawbacks
Adding new methods to the trait makes it even bigger, but I would argue that is a low cost (especially in this small case) for a nice benefit.
Rationale and alternatives
Another design could require the Iterator's Items to be of the type Result already for easier handling fallible maps. However, try_fold and try_for_each already exist and would be expected to behave similar. Changing stable methods would be bad and consistency is highly valuable. Additionally, the proposed implementation could easily deal with Result-items as well, as it offers the and_then method.
The proposed additions could of course be left out, but they are small additions. They are intuitive to use and blend into the already available picture with try_fold, try_find and try_for_each.
Instead of reacting on Result<bool, E> or Option<bool>, it could be of arbitrary type and check only whether the result was successful as per try. Though, all and any are constructed to check conditions on a set of items, returning a bool. Returned items by the iterator would be simply dropped. Consequently, using bools provides greater freedom to the user and is closer to the defintion of all and any.
Prior art
I do not know similar implementations in other languages. It is probably rare due to Rust's unique method of handling errors with Try, Option and Result.
There is the fallible_iterator crate, which re-invents the Iterator trait for fallible cases. It has try_all implemented named all, but it only works on Results and leads to a lot ambiguity in the usage of iterators.
There is this blog post about adding more fallible operations to iterators.
Unresolved questions
I do not know of any at this point in time.
Future possibilities
There is the option for many more methods for fallible iterator operation, as described in this blog post.
There is also the possibility to add map_ok and map_err to help handling iterators over Result items.