Fallible Iterators in Rust

resultit: A lightweight crate for iterating over Results

Benjamin Kay
robotic assembly line

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:

See the GitHub repository and the documentation for more information and examples. Result flattening based on the solution proposed by redditor earthenginein this post and on the rust playground. Please e-mail me with comments, suggestions, or if you found this crate helpful!