关于rust:当Iterator :: map返回Result :: Err时,如何停止迭代并返回错误?

How do I stop iteration and return an error when Iterator::map returns a Result::Err?

我有一个返回Result的函数:

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

然后再像这样使用它:

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

如何处理任何map迭代中的失败情况?

我知道我可以使用flat_map,在这种情况下,错误结果将被忽略:

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

Result的迭代器具有0或1个项目,具体取决于成功状态,而flat_map的迭代器将其过滤为0。

但是,我不想忽略错误,而是想使整个代码块停止并返回一个新错误(基于映射中出现的错误,或者仅转发现有错误)。

如何最好地在Rust中处理此问题?


Result实现了FromIterator,因此您可以将Result移到外部,并且迭代器将负责其余的工作(包括在发现错误时停止迭代)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[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);
}

游乐场


可接受的答案显示了如何在收集时停止错误,这很好,因为这是OP所要求的。如果您需要同样适用于大型或无限易错迭代器的处理,请继续阅读。

如前所述,for可以用来模拟错误停止,但是有时这并不优雅,例如当您要调用max()或其他使用方法时。在其他情况下,这几乎是不可能的,因为使用方法位于另一个板条箱中,例如itertools或Rayon1。


迭代器使用者:try_for_each

控制迭代器的使用方式时,只需使用try_for_each即可在出现第一个错误时停止。如果没有错误,它将返回结果Ok,否则返回Err,包含错误值:

1
2
3
4
5
6
7
8
9
10
11
12
13
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(())
}

如果需要在闭包调用之间保持状态,则也可以使用try_fold。这两种方法都是由ParallelIterator实现的,因此可以将它们与Rayon一起使用。

此方法要求您控制迭代器的使用方式。如果这是通过不受您控制的代码完成的-例如,如果要将迭代器传递给itertools::merge()或类似代码,则将需要一个适配器。
迭代器适配器:scan

在出错时停止的第一个尝试是使用take_while

1
2
3
4
5
6
7
8
9
10
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(())
}

这可行,但是我们没有任何迹象表明发生了错误,迭代只是默默地停止了。此外,它还需要难看的map(Result::unwrap),这使得程序似乎会因错误而惊慌,实际上情况并非如此,因为我们因错误而停止。

可以通过将take_while切换为scan来解决这两个问题,这是一个功能更强大的组合器,它不仅支持停止迭代,而且还传递其回调拥有的项,从而允许闭包将错误提取到调用方:铅>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(())
}

如果需要在多个地方使用,则可以将闭包抽象为实用函数:

1
2
3
4
5
6
7
8
9
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
        }
    }
}

...在这种情况下,我们可以将其作为.scan(&mut err, until_err)(操场)调用。

这些示例用for_each()穷尽了迭代器,但是可以使用任意操作(包括人造丝par_bridge())将其链接起来。使用scan()甚至可以将项目collect()放入容器中,并可以访问在错误之前看到的项目,这有时很有用,并且在收集到Result<Container, Error>中时不可用。

1使用Rayon并行处理流数据时,需要使用`par_bridge()`:

1
2
3
4
5
6
7
8
9
10
11
12
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)
}

同样,通过收集到Result不能轻易实现等效效果。


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

您可以为此使用std::result::fold功能。遇到第一个Err

后,它将停止迭代。

我刚刚编写的示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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))
    }
}

输出:

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

演示