Hey, rustaceans! I'm wondering if it's possible to collect an iterator of Result<Vec, Error> and, while doing it, flatten the data structure. That is, do it in a single iteration.
My current solution involves two iterations, hence why I'm wondering if this can be done in a more efficient way.
Here's some dummy code for illustrative purposes and a playground:
use std::error::Error;
use futures::future::join_all;
#[tokio::main]
async fn main() {
async fn duplicate(number: i32) -> Result<Vec<i32>, Box<dyn Error>> {
Ok(vec!(number * 2))
}
let my_numbers = vec!(1, 2, 3, 4, 5);
let future_duplicated_evens = my_numbers.into_iter().filter_map(|number| {
if number % 2 == 0 {
Some(duplicate(number))
} else {
None
}
}).collect::<Vec<_>>();
let flattened = join_all(future_duplicated_evens).await.into_iter().collect::<Result<Vec<_>, Box<dyn Error>>>().unwrap().into_iter().flatten().collect::<Vec<i32>>();
println!("Flattened: {:?}", flattened);
}
You can do it with itertools::process_results. The same kind of function (i.e. an equivalent std-internal function) is used in the implementation of FromIterator that allows .collecting into a Result<Vec<…>, _>.
use futures::future::join_all;
use itertools::{process_results, Itertools};
use std::error::Error;
#[tokio::main]
async fn main() {
async fn duplicate(number: i32) -> Result<Vec<i32>, Box<dyn Error>> {
Ok(vec![number * 2])
}
let my_numbers = vec![1, 2, 3, 4, 5];
let future_duplicated_evens = my_numbers
.into_iter()
.filter_map(|number| {
if number % 2 == 0 {
Some(duplicate(number))
} else {
None
}
})
.collect_vec();
let flattened = process_results(join_all(future_duplicated_evens).await, |i| {
i.flatten().collect_vec()
})
.unwrap();
println!("Flattened: {:?}", flattened);
}
You can use try_fold(..) to do it manually in one iteration.
I'm not sure if it would be more or less efficient to make a separate iteration counting the items first, to do fewer allocations, it probably depends on how the input looks.
Edit: Actually, concat uses fold1; it’s extending the first Vec item, so we’re even saving one allocation compared to the explicit try_fold approach. (Vec::new is only used in case the iterator is completely empty.)