Fallible Iterators in Rust
resultit: A lightweight crate for iterating over Results

Rust iterators and the associated framework of adapters/combinators are a powerful assembly line for transforming one sequence into another. The ResultIt crate provides an ergonomic way to handle iterators that can fail, i.e. iterators of Result that could countain an Error. Take, for example, the common problem of flattenning an iterator of nested results.
Background
If you've every written code like the following then you've used a rust iterator:
let v: Vec<i32> = vec![1, 2, 3];
for i in v {
println!("{}", i);
}
Rust iterators implement a function next()
that returns Option<Item>
where Item
is the type over which the iterator is iterating. The above example can be de-sugared as:let v: Vec<i32> = vec![1, 2, 3];
let iter = v.into_iter(); // satisfies trait bound Iterator<Item=i32>
while let Some(i) = iter.next() {
println!("{}", i); // i has type i32
}
// Output:
// 1
// 2
// 3
Here iter
is an iterator over the vector v
and satisfies the trait bound Iterator<Item=i32>
. The while let
statement executes as long as iter.next()
returns Some(i)
where i
is the next integer in the vector. After iter.next()
returns Some(3)
, the next call to iter.next()
returns None
and the while let
loop ends. Of course, usually you would use the more idiomatic constructs for i in vec
or for i in iter
or iter.for_each(|i| println!("{}", i)
and the Option<Item>
would be unwrapped for you automatically.
Rust iterators get even more interesting when we use adapters/combinators to transform them. Suppose we want to output each number in the vector plus one. We could write:
for i in v {
println!("{}", i + 1)
}
Or somewhat more generically using the map()
adapter/combinator:v.into_iter().map(|i| i + 1).for_each(|i| println!("{}", i));
Iterators of Results
As we all know, not everything in programming goes as planned, and iterators get more complicated when Item
is a Result
that could be an Err
. This can become especially problematic in chains of iterator adapter/combinators. What should each subsequent adapter/combinator do with an error it is not expecting? Take the following example, which does not compile:
let v: Vec<Result<i32, MyError>> = vec![1, MyError{}, 3];
v.into_iter().map(|i| i + 1).for_each(println!("{}", i));
// Doesn't compile!
This doesn't compile because we can't add 1 to a Result<i32,MyError>
without unwrapping it first. But if we simply call unwrap()
our code will compile, but it will panic on the first error, which is probably not what we want:let v: Vec<Result<i32, MyError>> = vec![1, MyError{}, 3];
v.into_iter().map(|i| i.unwrap() + 1).for_each(println!("{}", i));
// Output:
// 1
// Program panics!
The ResultIt Crate
If this problem looks familiar to you, then you may be interested in ResultIt, a lightweight, dependency-free, Rust crate I wrote that provides iterator adapter/combiantors for handling fallible iterators of Result
. It can help you with:
- Flattening an iterator of results, see flatten_results()
- Flattening or erasing error types in an iterator of nested results, see TryResult
- Stopping iteration after the first error, see stop_after_error()