Reputation: 30292
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?
Upvotes: 252
Views: 78089
Reputation: 155356
The accepted answer shows how to stop on error while collecting, as requested in the question. However it won't help with large or infinite fallible iterators, or when the iterator is collected by code you don't control. If your use case is one of those, 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.
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.
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 produced by the iterator, allowing the closure to extract the error:
fn main() -> io::Result<()> {
let mut err = Ok(());
fs::read_dir("/")?
.scan((), |_, item| item.map_err(|e| err = Err(e)).ok())
.for_each(|e| println!("{}", e.path().display()));
err?;
// ...
Ok(())
}
The closure passed to scan()
uses map_err()
to detect the error and extract it, and Result::ok()
to convert the useless Result::<T, ()>
returned by map_err()
to Some(T)
in case of Ok
, which continues the iteration, and None
in case of Err
, which terminates it.
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 not possible 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
.
Upvotes: 80
Reputation: 444
Adapted from From rust-by-example.
I use map_while() to stop iteration and return an error when the iterator map returns a result.
"map_while()
takes a closure as an argument. It will call this closure on each element of the iterator, and yield elements while it returns Some(_)."
fn is_valid_result()
checks if the result is valid.
use std::{error::Error, process};
/// From:
/// <https://doc.rust-lang.org/rust-by-example/error/iter_result.html#fail-the-entire-operation-with-collect>
fn main() -> Result<(), Box<dyn Error>> {
let s01 = vec!["-3", "0", "1", "2"];
let s02 = vec!["-1", "-2", "0"];
let s03 = vec!["3", "4", "tofu", "5"];
let s04 = vec!["6", "7", "8", "9"];
let data = [s01, s02, s03, s04];
/*
data.iter()
.enumerate()
.try_for_each(|(index, strings)| -> Result<(), Box<dyn Error>> {
println!("strings {:02}: {:?}", index + 1, strings);
let numbers: Vec<i32> = str_to_i32(&strings);
println!("integers {:02}: {:?}\n", index + 1, numbers);
Ok(())
})?;
*/
for (index, strings) in data.iter().enumerate() {
println!("strings {:02}: {:?}", index + 1, strings);
let numbers: Vec<i32> = str_to_i32(&strings);
println!("integers {:02}: {:?}\n", index + 1, numbers);
}
Ok(())
}
fn str_to_i32(strings: &[&str]) -> Vec<i32> {
strings
.iter()
.map(|s| s.parse::<i32>())
//.map_while(Result::ok)
.map_while(is_valid_result)
.collect()
}
/// Check if the result is valid.
fn is_valid_result<T, E>(result: Result<T, E>) -> Option<T>
where
E: std::fmt::Display,
{
match result {
Ok(integer) => Some(integer),
Err(why) => {
eprintln!("fn is_valid_result()");
eprintln!("Error: {why}");
process::exit(1);
}
}
}
See Rust Playground
Upvotes: 2
Reputation: 71430
If you do not want the allocation of collect()
, you are in a bit of a problem.
The problem is that you want your iterator to yield T
, but the code executing the iterator to get Result<T>
. How can you do that?
The result is to uplift the iterator into a callback, let the callback take an iterator returning T
, while the function calling the callback will return Result<T>
.
This is available in itertools
as process_results
:
// Use `process_results()` to emulate `collect::<Result<_, _>>()`.
// This is a for demonstration purposed only. Use `collect()` directly if you need to collect.
let parent_items: Result<Vec<Item>, ItemError> = parent_ids
.iter()
.map(|id| find(id))
.process_results(|iter| iter.collect::<Vec<Item>>());
Note the iterator cannot escape the callback (the borrow checker will ensure that).
It's also not that hard to implement it yourself:
pub trait IteratorExt: Iterator {
fn process_results<R, T, E>(
self,
f: impl FnOnce(ProcessResults<'_, Self, E>) -> R,
) -> Result<R, E>
where
Self: Iterator<Item = Result<T, E>>;
}
impl<I: Iterator> IteratorExt for I {
fn process_results<R, T, E>(
self,
f: impl FnOnce(ProcessResults<'_, Self, E>) -> R,
) -> Result<R, E>
where
Self: Iterator<Item = Result<T, E>>,
{
let mut err = None;
let p = ProcessResults {
err: &mut err,
iter: self,
};
let success = f(p);
err.map(Err).unwrap_or(Ok(success))
}
}
pub struct ProcessResults<'a, I, E> {
err: &'a mut Option<E>,
iter: I,
}
impl<T, E, I: Iterator<Item = Result<T, E>>> Iterator for ProcessResults<'_, I, E> {
type Item = T;
fn next(&mut self) -> Option<T> {
match self.iter.next() {
Some(Ok(item)) => Some(item),
Some(Err(err)) => {
*self.err = Some(err);
None
}
None => None,
}
}
}
Upvotes: 1
Reputation: 5405
.map()
closure Result
'sWhat 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 Result
s, 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 }
Upvotes: 7
Reputation: 222368
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!)
Upvotes: 1
Reputation: 15354
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);
}
Upvotes: 315