我有一个函数返回一个Result:
fn find(id: &Id) -> Result<Item, ItemError> {
// ...
}
Run Code Online (Sandbox Code Playgroud)
然后另一个使用它像这样:
let parent_items: Vec<Item> = parent_ids.iter()
.map(|id| find(id).unwrap())
.collect();
Run Code Online (Sandbox Code Playgroud)
如何在任何map迭代中处理失败的情况?
我知道我可以使用flat_map,在这种情况下,错误结果将被忽略:
let parent_items: Vec<Item> = parent_ids.iter()
.flat_map(|id| find(id).into_iter())
.collect();
Run Code Online (Sandbox Code Playgroud)
Result迭代器有0或1个项目,具体取决于成功状态,flat_map如果为0则会过滤掉它.
但是,我不想忽略错误,我想改为使整个代码块停止并返回一个新错误(基于映射中出现的错误,或者只是转发现有错误).
如何在Rust中最好地处理这个问题?
Bur*_*hi5 80
Result 实现FromIterator,所以你可以移动Result外部,迭代器将处理其余的(包括发现错误时停止迭代).
#[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);
}
Run Code Online (Sandbox Code Playgroud)
use*_*342 20
接受的答案显示了如何在收集时停止错误,这很好,因为这是 OP 要求的。如果您需要的处理也适用于大型或无限易出错的迭代器,请继续阅读。
如前所述,for可用于模拟 stop-on-error,但这有时是不雅的,例如当您想要调用max()或其他消耗方法时。在其他情况下,这几乎是不可能的,例如当消费方法在另一个板条箱中时,例如itertools或Rayon 1。
try_for_each当您控制迭代器的使用方式时,您可以使用try_for_each在第一个错误时停止。它接受一个返回 a 的闭包,如果闭包每次都返回,并且在第一个错误时返回第一个Result,try_for_each()它将返回。这允许闭包以自然的方式简单地使用运算符来检测错误:Ok(())OkErr?
use std::{fs, io};
fn main() -> io::Result<()> {
fs::read_dir("/")?.try_for_each(|e| -> io::Result<()> {
println!("{}", e?.path().display());
Ok(())
})?;
// ...
Ok(())
}
Run Code Online (Sandbox Code Playgroud)
如果您需要在闭包的调用之间维护状态,您还可以使用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(())
}
Run Code Online (Sandbox Code Playgroud)
这有效,但我们没有得到任何发生错误的迹象,迭代只是默默地停止。它还需要难看的东西map(Result::unwrap),这使得程序看起来会因错误而恐慌,实际上并非如此,因为我们会因错误而停止。
这两个问题都可以通过从take_whileto切换来解决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(())
}
Run Code Online (Sandbox Code Playgroud)
如果在多个地方需要,可以将闭包抽象为一个效用函数:
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
...在这种情况下,我们可以调用它作为.scan(&mut err, until_err)(操场)。
这些示例用 简单地耗尽了迭代器for_each(),但可以用任意操作链接它,包括 Rayon 的par_bridge()。使用scan()它甚至可以collect()将项目放入容器中并可以访问在错误之前看到的项目,这在收集到Result<Container, Error>.
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)
}
Run Code Online (Sandbox Code Playgroud)
同样,通过收集到Result.
.map()闭Result包如果我们有.map()a 内.map()a 内的 a该怎么办.map()?
.map()以下是嵌套操作的特定情况的示例。它解决的问题是如何从最里面的闭包传播失败,同时避免使用.unwrap()它来中止应用程序。
这种方法还允许?在发生错误时使用外层语法来捕获错误,或者如果没有发生错误,则将结果解包以分配给变量。?否则不能从封闭件内部使用。
.parse()正如下面使用的那样,将返回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(())
}
Run Code Online (Sandbox Code Playgroud)
请注意,外部.collect::<..>()通用表达式指定Result<Vec<Vec<..>>。内部.collect()将生成s,当外部获取内容并生成二维向量时Result,这些 s 会被外部剥离。ResultOk
如果不严重依赖类型推断,内部.collect()泛型表达式将如下所示:
.collect::<Result<Vec<i32>, _>>()) // <--- Inner.
.collect::<Result<Vec<Vec<i32>>, _>>()?; // <--- Outer.
Run Code Online (Sandbox Code Playgroud)
使用?语法,变量 ,data将被分配这个二维向量;否则该main()函数将返回源自内部闭包的解析错误。
输出:
[[1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)
更进一步,可以通过这种方式处理嵌套三层深度的解析结果。
[[1, 2, 3, 4], [5, 6, 7, 8]]
Run Code Online (Sandbox Code Playgroud)
输出:
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
Run Code Online (Sandbox Code Playgroud)
或者如果一个数字无法解析,我们会得到:
Error: ParseIntError { kind: InvalidDigit }
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7484 次 |
| 最近记录: |