接受的答案显示了如何在收集时停止出错,这很好,因为这是 OP 要求的。如果您需要的处理也适用于大型或无限易错迭代器,请继续阅读。
如前所述,for 可用于模拟错误停止,但这有时并不优雅,例如当您想调用max() 或其他消耗方法时。在其他情况下,这几乎是不可能的,例如消费方法在另一个 crate 中,例如 itertools 或 Rayon1。
迭代器消费者:try_for_each
当您控制迭代器的使用方式时,您可以使用try_for_each 在第一个错误时停止。它接受一个返回Result 的闭包,如果闭包每次都返回Ok,try_for_each() 将返回Ok(()),第一个错误是第一个Err。这允许闭包以自然的方式简单地使用? 运算符来检测错误:
use std::{fs, io};
fn main() -> io::Result<()> {
fs::read_dir("/")?.try_for_each(|e| -> io::Result<()> {
println!("{}", e?.path().display());
Ok(())
})?;
// ...
Ok(())
}
如果你需要在闭包的调用之间保持状态,你也可以使用try_fold。这两种方法都是由ParallelIterator 实现的,所以同样的模式适用于Rayon。
这种方法要求您控制迭代器的使用方式。如果这是由不受您控制的代码完成的 - 例如,如果您将迭代器传递给 itertools::merge() 或类似的,您将需要一个适配器。
迭代器适配器:scan
第一次尝试停止错误是使用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(())
}
这可行,但我们没有得到任何错误指示,迭代只是静默停止。它还需要难看的map(Result::unwrap),这使得程序似乎会在错误时恐慌,实际上并非如此,因为我们在错误时停止。
这两个问题都可以通过从take_while 切换到scan 来解决,这是一个更强大的组合器,不仅支持停止迭代,而且传递其回调拥有的项目,允许闭包将错误提取给调用者:
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(())
}
如果在多个地方需要,可以将闭包抽象为一个实用函数:
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) (playground)。
这些示例用for_each() 简单地耗尽了迭代器,但可以使用任意操作将其链接起来,包括 Rayon 的par_bridge()。使用 scan() 甚至可以将 collect() 项目放入容器中,并可以访问在错误之前看到的项目,这在收集到 Result<Container, Error> 时有时很有用且不可用。
1 使用 Rayon 并行处理流数据时需要使用 `par_bridge()`:
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 无法轻易实现等效效果。