9

I would like to do something like this:

enum MyEnum {
    Foo(Vec<Stuff>),
    // ...
}

impl MyEnum {
    fn do_sth(&self) -> Result<Bar, E> {
        match *self {
            MyEnum::Foo(ref vec) => {
                let (a, b): (Vec<A>, Vec<B>) = vec
                    .iter()
                    .map(|thing| thing.make_a_tuple(arg)) // returns Result<(A, B), E>
                    .collect::<Result<_, _>>()? // stop on first error
                    .unzip();

                // use a and b
            }

            // other cases
        }

        // ...
    }
}

This fails to compile with error: the type of this value must be known in this context. Through trial and error, I got it to compile like this

    fn do_sth(&self) -> Result<Bar, E> {
        match *self {
            MyEnum::Foo(ref vec) => {
                let (a, b): (Vec<A>, Vec<B>) = vec
                    .iter()
                    .map(|thing| thing.make_a_tuple(arg))
                    .collect::<Result<Vec<_>, _>>()?
                    .into_iter()
                    .unzip();

                // use a and b
            }

            // other cases
        }
    }

However, I would like to avoid the unnecessary allocation of a Vec<(A, B)>.

Is it possible to do this without intermediate allocations? I am sure I could do this myself with a loop, but I would prefer to learn the Rusty way.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • "I don't fully understand why collect does the right thing when collecting a `Vec>`" That would make a separate question, which fortunately has already been answered [here](https://stackoverflow.com/a/26370894/1233251). – E_net4 Jun 14 '17 at 12:31
  • As for this matter, you have indeed stumbled upon an intriguing challenge. `unzip` behaves pretty much _unlike_ `collect`: explicitly according to the API, unzip will create two empty containers and shove data into them with `extend`. This means that it cannot do special tricks with `Result` – E_net4 Jun 14 '17 at 12:56
  • This is indeed helpful, thank you. But can I collect into something like Result? Would this even make sense? Edit: Your second comment arrived on my screen just as I was posting my reply. I think I'll just keep things simple with a hand written loop. – novice-rusticean Jun 14 '17 at 13:04

1 Answers1

7

I've run into this type of problem with an iterator of Results so commonly that I added a (private) helper type to the standard library called ResultShunt. This powers the implementation of Sum and Product for Results.

I also submitted it to itertools so it can be reused:

use itertools; // 0.10.1

fn main() {
    let iter_source: Vec<Result<(i32, i32), bool>> = vec![Ok((1, 2)), Err(false), Ok((3, 4))];

    let z = itertools::process_results(iter_source, |iter| iter.unzip::<_, _, Vec<_>, Vec<_>>());

    println!("{:?}", z);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366