Announcing result_iter 0.1

Here's a little crate with a solution to a problem I've encountered a number of times. I've had this sitting on my disk for a while, but it's basically done, so publishing it now to see what people think. I haven't actually used this in real code, and there may well be much better solutions for what it's trying to do.

result_iter 0.1.

Fairly often I am building a vector over fallible operations, resulting in iterating over a bunch of Results. What I really want though is not a collection of Result<T> but a collection of T. It hasn't been obvious to me how to deal with those situations ergonomically.

So this crate provides a one-liner that either: extracts every element of the iterator over Result<T> into an iterator over T; or, returns an error.

The fail_fast_if_err method stops at the first error, while the fail_slow_if_err iterates over all elements and provides access to every error in the collection.

Here's a full example:

extern crate result_iter;

use result_iter::ResultIterExt;
use std::io::{self, Read, BufReader};
use std::fs::{self, File};

fn run() -> Result<Vec<String>, io::Error> {
    // Read a directory of files into a Vec, where each
    // file my generate an error
    let maybe_strings;
    maybe_strings = fs::read_dir(".")?
        .map(|dirent| {
            dirent.and_then(|d| File::open(d.path()))
                .and_then(|f| {
                    let mut f = BufReader::new(f);
                    let mut s = String::new();
                    f.read_to_string(&mut s)?;
            Ok(s)})
        });

    // As soon as we encounter an error, return it.
    // Otherwise return a Vec<String>
    let strings = maybe_strings.fail_fast_if_err()?.collect();
    Ok(strings)
}

fn main() {
    let _ = run();
}

Note that these methods, though they accept iterators and return iterators, need to drain the original iterator, scanning for errors, and so use temporary storage proportional to the number of elements.

What do you think? What other ways are there to solve this problem?

6 Likes

I would normally use impl<A, E, V> FromIterator<Result<A, E>> for Result<V, E> where V: FromIterator<A>.

- let strings = maybe_strings.fail_fast_if_err()?.collect();
- Ok(strings)
+ maybe_strings.collect()
6 Likes

Aside from what @dtolnay said, the memory allocation required by both collect and result_iter is enough to make me like this solution the best:

use std::io::{self, Read, BufReader};
use std::fs::{self, File};

fn run() -> Result<Vec<String>, io::Error> {
    let mut strings = vec![];
    for result in fs::read_dir(".")? {
        let dirent = result?;
        let mut f = BufReader::new(File::open(dirent.path())?);
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        strings.push(s);
    }
    Ok(strings)
}

fn main() {
    let _ = run();
}

In this case, run is returning an allocation proportional to the number of elements yielded by read_dir, so it's not particularly compelling. But I find that when I'm actually using things like read_dir in practice, the intermediate memory allocation is unnecessary, which pushes me toward the aforementioned code.

In test code though, I make liberal use of the FromIterator overloading. :slight_smile:

1 Like

TIL

3 Likes