That zip
version isn't quite correct: if b
returns None
, the original version will have already output the last a
value, whereas the zip
version will discard the a
value as soon as b
ends. To get the correct output, we have to do something wonkier:
use std::fmt::Debug;
fn bar<T, E, A, B>(a: A, b: B) -> Result<(), E>
where
T: Debug,
A: IntoIterator<Item = Result<T, E>>,
B: IntoIterator<Item = Result<T, E>>,
{
let a = a.into_iter();
let mut b = b.into_iter();
a.map(|x| (x, b.next()))
.try_for_each(|(x, y)| {
let x = x.map_err(Err)?;
println!("a => {x:?}");
let y = y.ok_or(Ok(()))?.map_err(Err)?;
println!("b => {y:?}");
Ok(())
})
.map_or_else(|result| result, |_| Ok(()))
}
But that still isn't entirely equivalent in behavior, since it will pull an extra element from b
even after a
returns Some(Err)
. We could instead use Itertools::interleave_shortest()
:
use itertools::Itertools;
use std::fmt::Debug;
fn bar<T, E, A, B>(a: A, b: B) -> Result<(), E>
where
T: Debug,
A: IntoIterator<Item = Result<T, E>>,
B: IntoIterator<Item = Result<T, E>>,
{
let a = a.into_iter().map(|x| ("a", x));
let b = b.into_iter().map(|x| ("b", x));
a.interleave_shortest(b).try_for_each(|(src, x)| {
let x = x?;
println!("{src} => {x:?}");
Ok(())
})
}
I'll admit that this does get even more unwieldy with your last example. What I'd do to generalize this is to create a small state machine to pull from the iterators in the correct sequence:
use std::{fmt::Debug, iter};
fn bar<T, E, A, B>(a: A, b: B) -> Result<(), E>
where
T: Debug,
A: IntoIterator<Item = Result<T, E>>,
B: IntoIterator<Item = Result<T, E>>,
{
let mut a = a.into_iter();
let mut b = b.into_iter();
let mut i = 0;
iter::from_fn(|| {
if i < 2 {
i += 1;
a.next().map(|x| ("a", x))
} else {
i = 0;
b.next().map(|x| ("b", x))
}
})
.try_for_each(|(src, x)| {
let x = x?;
println!("{src} => {x:?}");
Ok(())
})
}
I concur with @J-Cake, that generators would be very useful here on the pulling end.
Anyway, after doing this exercise, I think that the main limitation of try_for_each()
here is that there's only one cancellation point after each element: you can't partially pull a value from the iterator. This is mainly relevant when you're pulling from one iterator, and only conditionally pulling from another iterator. Of course, the pulling logic could also be split into a bunch of map()
s or map_while()
s before the final try_for_each()
.