在学习Rust的过程中,我熟悉了错误传播以及unwrap
和?
运算符之间的选择。在编写了一些仅使用unwrap()
的原型代码之后,我想从可重复使用的部分中删除unwrap
,其中对每个错误的恐慌是不合适的。
如何避免在闭包中使用unwrap
,就像在这个例子中一样?
// todo is VecDeque<PathBuf>
let dir = fs::read_dir(&filename).unwrap();
todo.extend(dir.map(|dirent| dirent.unwrap().path()));
第一个unwrap
可以很容易地改为?
,只要包含函数返回Result<(), io::Error>
或类似。然而,第二个unwrap
,dirent.unwrap().path()
中的那个,不能改为dirent?.path()
,因为封闭必须返回PathBuf
,而不是Result<PathBuf, io::Error>
。
一种选择是将extend
更改为显式循环:
let dir = fs::read_dir(&filename)?;
for dirent in dir {
todo.push_back(dirent?.path());
}
但这感觉不对 - 原来的extend
很优雅,清楚地反映了代码的意图。 (它可能比push_back
s序列更有效。)经验丰富的Rust开发人员如何在这些代码中表达错误检查?
如何避免在闭包中使用
unwrap
,就像在这个例子中一样?
嗯,这真的取决于你在失败时想做什么。
例如,您可以完美地决定以静默方式忽略所有故障,并跳过失败的条目。在这种情况下,Iterator::filter_map
与Result::ok
结合正是你所要求的。
let dir = fs::read_dir(&filename)?;
let todos.extend(dir.filter_map(Result::ok));
Iterator
界面充满了好东西,在寻找更整洁的代码时绝对值得细读。
这是一个基于filter_map
建议的Matthieu的解决方案。它调用Result::map_err
以确保错误被“捕获”并记录,将其发送到Result::ok
和filter_map
以从迭代中删除它:
fn log_error(e: io::Error) {
writeln!(&mut std::io::stderr(), "{}", e).unwrap()
}
(|| {
let dir = fs::read_dir(&filename)?;
todo.extend(dir
.filter_map(|res| res.map_err(log_error).ok()))
.map(|dirent| dirent.path()));
})().unwrap_or_else(log_error)