209

I have a function that returns a Result:

fn find(id: &Id) -> Result<Item, ItemError> {
    // ...
}

Then another using it like this:

let parent_items: Vec<Item> = parent_ids.iter()
    .map(|id| find(id).unwrap())
    .collect();

How do I handle the case of failure inside any of the map iterations?

I know I could use flat_map and in this case the error results would be ignored:

let parent_items: Vec<Item> = parent_ids.iter()
    .flat_map(|id| find(id).into_iter())
    .collect();

Result's iterator has either 0 or 1 items depending on the success state, and flat_map will filter it out if it's 0.

However, I don't want to ignore errors, I want to instead make the whole code block just stop and return a new error (based on the error that came up within the map, or just forward the existing error).

How do I best handle this in Rust?

Kai Sellgren
  • 27,954
  • 10
  • 75
  • 87

4 Answers4

258

Result implements FromIterator, so you can move the Result outside and iterators will take care of the rest (including stopping iteration if an error is found).

#[derive(Debug)]
struct Item;
type Id = String;

fn find(id: &Id) -> Result<Item, String> {
    Err(format!("Not found: {:?}", id))
}

fn main() {
    let s = |s: &str| s.to_string();
    let ids = vec![s("1"), s("2"), s("3")];

    let items: Result<Vec<_>, _> = ids.iter().map(find).collect();
    println!("Result: {:?}", items);
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
BurntSushi5
  • 13,917
  • 7
  • 52
  • 45
  • 15
    +1 This is awesome! (The example from my answer ported to this: http://is.gd/E26iv9) – Dogbert Oct 15 '14 at 10:21
  • If my own type implements `FromIterator`, can I apply this same trick? Is this limited to `FromIterator` or could it be applied in a broader way? How does Rust know to use `FromIterator`? – Kai Sellgren Oct 15 '14 at 16:51
  • 1
    @KaiSellgren Yes, you can apply this same trick. The key is in the type signature of [`collect`](http://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.collect), which is polymorphic on the return type, which must implement `FromIterator`. I don't know what you mean by "can it be applied in a broader way." Rust supports polymorphic return types... So, yes? (See the [`Rng`](http://doc.rust-lang.org/rand/trait.Rng.html) and [`Default`](http://doc.rust-lang.org/std/default/trait.Default.html) traits for more examples of return type polymorphism.) – BurntSushi5 Oct 16 '14 at 10:41
  • That makes sense, but how does it know to call `from_iter()` method? http://doc.rust-lang.org/std/iter/trait.FromIterator.html – Kai Sellgren Oct 16 '14 at 16:42
  • 5
    @KaiSellgren `from_iter` is called in the [`collect`](http://doc.rust-lang.org/src/core/home/rustbuild/src/rust-buildbot/slave/nightly-linux/build/src/libcore/iter.rs.html#452-465) method. – BurntSushi5 Oct 16 '14 at 20:39
  • 2
    Use of `collect()` requires the iterator to be finite, correct? If so, how would a similar but infinite iterator be handled? – U007D Mar 04 '19 at 21:36
  • Sounds like a different question to me. Use a for loop? – BurntSushi5 Mar 13 '19 at 14:39
  • 5
    What would you do in case of multiple `map()`s? If the first `map()` returns a `Result`, then the following `map()` has to accept a `Result` as well which can be annoying. Is there a way to achieve the same from the middle of `map()` chain? Short of just doing `.map(...).collect, _>>()?.into_iter().map(...)`, of course. – Good Night Nerd Pride Jun 21 '20 at 13:10
  • 1
    Rust By Example has a section on this, maybe link to it in your answer: https://doc.rust-lang.org/rust-by-example/error/iter_result.html#fail-the-entire-operation-with-collect – Levi Morrison Jan 17 '23 at 21:13
64

The accepted answer shows how to stop on error while collecting, and that's fine because that's what the OP requested. If you need processing that also works on large or infinite fallible iterators, read on.

As already noted, for can be used to emulate stop-on-error, but that is sometimes inelegant, as when you want to call max() or other method that consumes the iterator. In other situations it's next to impossible, as when the iterator is consumed by code in another crate, such as itertools or Rayon1.

Iterator consumer: try_for_each

When you control how the iterator is consumed, you can just use try_for_each to stop on first error. It accepts a closure that returns a Result, and try_for_each() will return Ok(()) if the closure returned Ok every time, and the first Err on the first error. This allows the closure to detect errors simply by using the ? operator in the natural way:

use std::{fs, io};

fn main() -> io::Result<()> {
    fs::read_dir("/")?.try_for_each(|e| -> io::Result<()> {
        println!("{}", e?.path().display());
        Ok(())
    })?;
    // ...
    Ok(())
}

If you need to maintain state between the invocations of the closure, you can also use try_fold. Both methods are implemented by ParallelIterator, so the same pattern works with Rayon.

try_for_each() does require that you control how the iterator is consumed. If that is done by code not under your control - for example, if you are passing the iterator to itertools::merge() or similar, you will need an adapter.

Iterator adapter: scan

The first attempt at stopping on error is to use take_while:

use std::{io, fs};

fn main() -> io::Result<()> {
    fs::read_dir("/")?
        .take_while(Result::is_ok)
        .map(Result::unwrap)
        .for_each(|e| println!("{}", e.path().display()));
    // ...
    Ok(())
}

This works, but we don't get any indication that an error occurred, the iteration just silently stops. Also it requires the unsightly map(Result::unwrap) which makes it seem like the program will panic on error, which is in fact not the case as we stop on error.

Both issues can be addressed by switching from take_while to scan, a more powerful combinator that not only supports stopping the iteration, but passes its callback owned items, allowing the closure to extract the error to the caller:

fn main() -> io::Result<()> {
    let mut err = Ok(());
    fs::read_dir("/")?
        .scan(&mut err, |err, res| match res {
            Ok(o) => Some(o),
            Err(e) => {
                **err = Err(e);
                None
            }
        })
        .for_each(|e| println!("{}", e.path().display()));
    err?;
    // ...
    Ok(())
}

If needed in multiple places, the closure can be abstracted into a utility function:

fn until_err<T, E>(err: &mut &mut Result<(), E>, item: Result<T, E>) -> Option<T> {
    match item {
        Ok(item) => Some(item),
        Err(e) => {
            **err = Err(e);
            None
        }
    }
}

...in which case we can invoke it as .scan(&mut err, until_err) (playground).

These examples trivially exhaust the iterator with for_each(), but one can chain it with arbitrary manipulations, including Rayon's par_bridge(). Using scan() it is even possible to collect() the items into a container and have access to the items seen before the error, which is sometimes useful and unavailable when collecting into Result<Container, Error>.


1 Needing to use par_bridge() comes up when using Rayon to process streaming data in parallel:

fn process(input: impl BufRead + Send) -> std::Result<Output, Error> {
    let mut err = Ok(());
    let output = lines
        .input()
        .scan(&mut err, until_err)
        .par_bridge()
        .map(|line| ... executed in parallel ... )
        .reduce(|item| ... also executed in parallel ...);
    err?;
    ...
    Ok(output)
}

Again, equivalent effect cannot be trivially achieved by collecting into Result.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 2
    https://docs.rs/itertools/0.9.0/itertools/fn.process_results.html – Shepmaster Jul 27 '20 at 17:07
  • *as when you want to `sum()` [...] the `Ok` items* — that's already implemented in the standard library, using the same technique as the `process_results` method in itertools. – Shepmaster Jul 27 '20 at 17:08
  • 1
    @Shepmaster I didn't know about `process_results()`, thanks. Its upside is that it doesn't require a separate error variable. Its downsides are that it's only available as a top-level function that calls you (possible issue when iterating over several things in parallel), and that it requires an external crate. The code in this answer is reasonably short, works with stdlib, and participates in iterator chaining. – user4815162342 Jul 27 '20 at 17:15
5

Handling nested .map() closure Result's

What if we have a .map() within a .map() within a .map()?

Here's an example for the specific case where the .map() operations are nested. The problem it solves is how to propagate a failure from the innermost closure while avoiding using .unwrap() which aborts the application.

This approach also enables using ? syntax at the outer layer to capture the error if one occurs, or unwrap the result to assign to a variable if no error occurred. ? can't otherwise be used from inside the closures.

.parse() as it's used below will return Result<T, ParseIntError>.

use std::error::Error;

const DATA: &str = "1 2 3 4\n5 6 7 8";

fn main() -> Result<(), Box<dyn Error>>
{
    let data = DATA.lines().map(|l| l.split_whitespace()
                                     .map(|n| n.parse() /* can fail */)
                                     .collect())
                           .collect::<Result<Vec<Vec<i32>>, _>>()?;
    println!("{:?}", data);
    Ok(())
}

Note that the outer .collect::<..>() generic expression specifies Result<Vec<Vec<..>>. The inner .collect() will be producing Results, which are stripped away by the outer Result as it takes the Ok contents and produces the 2-D vector.

Without relying heavily on type inference, the inner .collect() generic expression would look like this:

          .collect::<Result<Vec<i32>, _>>()) // <--- Inner.
    .collect::<Result<Vec<Vec<i32>>, _>>()?; // <--- Outer.

Using the ? syntax, the variable, data, will be assigned this 2-D vector; or the main() function will return a parsing error that originated from within the inner closure.

output:

[[1, 2, 3, 4], [5, 6, 7, 8]]

Taking it a step further, parse results nested three levels deep can be handled this way.

type Vec3D<T, E> = Result<Vec<Vec<Vec<T>>>, E>;

const DATA: &str = "1 2 | 3 4\n5 6 | 7 8";

fn main() -> Result<(), Box<dyn Error>>
{
    let data = DATA.lines()
                   .map(|a| a.split("|")
                             .map(|b| b.split_whitespace()
                                       .map(|c| c.parse()) // <---
                                       .collect())
                             .collect())
                   .collect::<Vec3D<i32,_>>()?;
    println!("{:?}", data);
    Ok(())
}

output:

[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

Or if a number couldn't be parsed, we'd get:

Error: ParseIntError { kind: InvalidDigit }
Todd
  • 4,669
  • 1
  • 22
  • 30
  • How does the outer Result remove inner Result? Seems kinda like a magic to me. What's happening underneath? – Dulguun Otgon Aug 17 '23 at 14:40
  • 1
    @DulguunOtgon, the compiler's type inference features use the type declared as `Vec3D` to figure out how to parse the nested `.map()` return values. Notice the call to `.collect()` on the `lines` iterator, which specifies the return type. – Todd Aug 20 '23 at 05:18
  • 1
    @DulguunOtgon, `Result` implements `FromIterator` in such a way that it can receive the `Result` objects of an internal `.map()` iterator and create a new `Result` object.. if any of the internal iterators returns an error `Result` value, it will propagate up the layers. If someone can explain this better, feel free to chime in. – Todd Aug 20 '23 at 05:22
  • Oh, do you mean https://doc.rust-lang.org/std/result/enum.Result.html#impl-FromIterator%3CResult%3CA,+E%3E%3E-for-Result%3CV,+E%3E – Dulguun Otgon Aug 28 '23 at 00:00
2

This answer pertains to a pre-1.0 version of Rust and the required functions were removed

You can use std::result::fold function for this. It stops iterating after encountering the first Err.

An example program I just wrote:

fn main() {
  println!("{}", go([1, 2, 3]));
  println!("{}", go([1, -2, 3]));
}

fn go(v: &[int]) -> Result<Vec<int>, String> {
    std::result::fold(
        v.iter().map(|&n| is_positive(n)),
        vec![],
        |mut v, e| {
            v.push(e);
            v
        })
}

fn is_positive(n: int) -> Result<int, String> {
    if n > 0 {
        Ok(n)
    } else {
        Err(format!("{} is not positive!", n))
    }
}

Output:

Ok([1, 2, 3])
Err(-2 is not positive!)

Demo

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dogbert
  • 212,659
  • 41
  • 396
  • 397