接受的答案展示了如何在收集期间停止错误,这很好,因为这是 OP 请求的。如果您需要处理也适用于大型或无限不可靠迭代器,则请继续阅读。
如已经指出的那样,可以使用for
来模拟停止错误,但有时这是不优雅的,比如当您想要调用max()
或其他消耗迭代器的方法时。在其他情况下,这几乎是不可能的,例如当迭代器被另一个包中的代码(例如 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。
try_for_each()
确实要求您控制迭代器的消耗方式。如果由您无法控制的代码完成-例如,如果您将迭代器传递给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()
甚至可以将项目收集到容器中并访问之前看到的项目,在将其收集到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
中无法轻松实现等效效果。
from_iter
方法在collect
方法中被调用。具体可以参考链接:collect
。 - BurntSushi5collect()
需要迭代器是有限的,对吗?如果是这样,那么类似但无限的迭代器该如何处理? - U007Dmap()
,当第一个map()
返回一个Result
时,后续的map()
也必须接收一个Result
,这可能会很麻烦。是否有一种方法可以在map()
链的中间实现相同的效果?当然,除了使用.map(...).collect<Result<Vec<_>, _>>()?.into_iter().map(...)
这种方式。 - Good Night Nerd Pride