Multiple fallible maps without `.collect` calls or allocation?

I'm currently working on a project which requires ingesting, normalizing, processing, and maximizing some data. I expected to be able to write a simple pipeline like

fn process(input) -> Result<FinalAnswer, ErrorType> {
    Ok(ingest(input)?.into_iter().map(normalize)?.map(process)?.fold(maximize))
}

(obviously, this is pseudocode). In reality, because each of ingest, normalize, and process can fail and thus return a Result, I had to .collect() each one to flatten the output before I could use the ? operator (or any other early return method).

As reproduced in the playground, this is very ugly and introduces multible extra Vecs that I don't want.

What is the idiomatic, non-allocating way to do this, if there is one?

The reason you can't do map(normalize)? is because this just returns a lazy Map iterator -- there aren't any Result items until something down the chain starts pulling items.

But you can ? the results within the following map, and then use the new try_fold:

fn compute(input: String) -> Result<i32, String> {
    ingest(input)? // Ingest and abort if error. Simple enough
        .into_iter()
        .map(|i| i.normalize()) // Maps into Result<Data, String>
        .map(|i| i?.perform_operation()) // Maps into Result<i32, String>
        .try_fold(0, |current_max, value| Ok(current_max.max(value?))) // Maximize
}
5 Likes

Ah, thank you! That makes sense. I didn't know about try_fold.