iterator<Result<ok,err>> to Result<iterator<ok>, err>

hello,

here is the question : i’m writing code where a map closure returns a Result<…,&str>… How should I handle errors ?

let’s consider an example to illustrate my question (I’ve search on the forum but not found any answer, I must be the only one struggling with such basic stuff QQ) :

struct A {
    is_ok: bool
}

impl A {
    fn to_b(&self) -> Result<B, &'static str> {
        if self.is_ok {
            Ok(B {})
        } else {
            Err("err")
        }
    }
}

struct B {}

trait ToBs<E> {
    fn to_bs(&self) -> Result<Vec<B>, E>;
}

impl ToBs<&str> for Vec<A> {
    fn to_bs(&self) -> Result<Vec<B>, &'static str> {
        Err("part to implement")
    }
}

pub fn test() {
    let result = vec![A { is_ok: true }, A { is_ok: false }].to_bs();
}

first direct approach (but horrible approach) :

impl ToBs<&str> for Vec<A> {
    fn to_bs(&self) -> Result<Vec<B>, &'static str> {
        let (oks, errs): (Vec<Result<B, &str>>, Vec<Result<B, &str>>) = self.iter()
            .map(|a| a.to_b())
            .partition(|res| res.is_ok());

        if let Some(err) = errs.into_iter().filter_map(|err| err.err()).next() {
            return Err(err);
        }

        Ok(oks.iter().filter_map(|res| res.ok()).collect())
    }
}

second approach using try_fold:

impl ToBs<&str> for Vec<A> {
    fn to_bs(&self) -> Result<Vec<B>, &'static str> {
        self.iter()
            .map(|a| a.to_b())
            .try_fold(Vec::new(), |mut acc, b_res| {
                b_res.map(|b|{
                    acc.push(b);
                    acc
                })
            })
    }
}

but it feels like i’m reinventing the wheel…

I’d like to write something like this :

impl ToBs<&str> for Vec<A> {
    fn to_bs(&self) -> Result<Vec<B>, &'static str> {
        self.iter()
            .try_map(|a| a.to_b())?
            .collect()
    }
}

with try_map returning something like Result<dyn Iterator<B>, Err>… But I’m not able to code the appriopriate trait & impl…

feels bad not beeing able to map values and handle error (when I know how to do this in like 10 other languages ;-(

1 Like

Looks like Rewrite help: imperative -> functional style

The function you are looking from is quite hidden behind a very specific trait, FromIterator, the one that allows .collecting an Iterator into.

That trait is implemented to deal with an Iterator<Item = Result> precisely to collect the Results like you wanted:

impl ToBs<&str> for Vec<A> {
    fn to_bs(&self) -> Result<Vec<B>, &'static str> {
        self.iter()
            .map(A::to_b)
            .collect()
    }
}
4 Likes

I don’t doubt the correctness of your solution – but I have a contradiction in my understanding of iterators:

  1. I thought iterators were lazy.

  2. To go from iterator<Result<ok, err>> to Result<iterator<ok>, err> … it seems the very act of the 2nd (knowing whether it’s a ok or an err) requires consuming the first until either err or completion.

  3. What happens on infinite streams? On finite streams, is the iterator no longer lazy?

1 Like

Indeed, the resulting Iterator can no longer be lazy, it must be some form of collection.

This is a more general “problem” with .collect(): with [try_]for_each, [try_]fold or manual iteration, they are the consumers of the indeed lazy iterators, thus forcing them then to yield as many elements as the consumer requires (for .collect, .fold(), .for_each this will try to exhaust the Iterator).

That’s why only finite iterators are the ones .collected; for instance, an iterator that originates from a slice, like in the OP’s case.

1 Like

I believe you are right on 3 points. So iterator -> result<iterator,e> has to sacrify lazynes.

Actually solving this with the collector makes more sense

For different strategies for handling errors with iterators, see this blog post which has been summarized on Rust By Example.