How do I stop iteration and return an error when Iterator::map returns a Result::Err?
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
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 consuming method. In other situations it's next to impossible, as when the consuming method is 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 will return a result that is Ok
if there was no error, and is Err
otherwise, containing the error value:
use std::{io, fs};
fn main() -> io::Result<()> {
fs::read_dir("/")?
.take_while(Result::is_ok)
.map(Result::unwrap)
.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 you can use them with Rayon.
This approach requires 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 fixed 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
.