处理/展开嵌套结果类型的惯用方法是什么?

问题描述 投票:0回答:2

我读到,在

unwrap
上使用
Result
在 Rust 中并不是一个好的做法,最好使用模式匹配,这样发生的任何错误都可以得到适当的处理。

我明白了,但请考虑这个读取目录并打印每个条目的访问时间的片段:

use std::fs;
use std::path::Path;

fn main() {
    let path = Path::new(".");
    match fs::read_dir(&path) {
        Ok(entries) => {
            for entry in entries {
                match entry {
                    Ok(ent) => {
                        match ent.metadata() {
                            Ok(meta) => {
                                match meta.accessed() {
                                    Ok(time) => {
                                        println!("{:?}", time);
                                    },
                                    Err(_) => panic!("will be handled")
                                }
                            },
                            Err(_) => panic!("will be handled")
                        }
                    },
                    Err(_) => panic!("will be handled")
                }
            }
        },
        Err(_) => panic!("will be handled")
    }
}

我想处理上面代码中所有可能的错误(

panic
宏只是一个占位符)。虽然上面的代码可以工作,但我认为它很丑陋。处理这种情况的惯用方法是什么?

rust error-handling idioms
2个回答
20
投票

我读到,在 Result 上使用

unwrap
在 Rust 中并不是一个好的做法。

这并不容易。例如,阅读我的回答以了解更多信息。现在解决您的主要问题:


通过将
Ok
值传递到外部来减少右移

代码的一个大问题是右移:例如,

meta.accessed()
调用缩进了很多。我们可以通过从
match
:

中传递我们想要使用的值来避免这种情况
let entries = match fs::read_dir(&path) {
    Ok(entries) => entries, // "return" from match
    Err(_) => panic!("will be handled"),
};

for entry in entries {  // no indentation! :)
    // ...
}

这已经是使代码更具可读性的一个非常好的方法了。

使用
?
运算符将错误传递给调用函数

您的函数可以返回

Result<_, _>
类型,以便将错误传递给调用函数(是的,甚至
main()
也可以返回
Result
)。在这种情况下,您可以使用
?
运算符:

use std::{fs, io};

fn main() -> io::Result<()> {
    for entry in fs::read_dir(".")? {
        println!("{:?}", entry?.metadata()?.accessed()?);
    }
    Ok(())
}

使用
Result

的辅助方法

对于 map()

 类型,还有许多辅助方法,例如 
and_then()
Result
。如果您想做某事,如果结果是 
and_then
并且该操作将返回相同类型的结果,那么
Ok
会很有帮助。这是带有
and_then()
的代码以及错误的手动处理:

fn main() {
    let path = Path::new(".");
    let result = fs::read_dir(&path).and_then(|entries| {
        for entry in entries {
            let time = entry?.metadata()?.accessed()?;
            println!("{:?}", time);
        }
        Ok(())
    });

    if let Err(e) = result {
        panic!("will be handled");
    }
}

确实不止一种方法可以进行此类错误处理。您必须了解可以使用的所有工具,然后需要选择最适合您情况的工具。但是,在大多数情况下,

?
运算符是正确的工具。


4
投票

Result
恰好有很多方便的方法用于此类事情:

use std::fs;
use std::path::Path;

fn main() {
    let path = Path::new(".");
    match fs::read_dir(&path) {
        Ok(entries) => {
            for entry in entries {
                match entry.and_then(|e| e.metadata()).map(|m| m.accessed()) {
                    Ok(time) => {
                        println!("{:?}", time);
                    },
                    Err(_) => panic!("will be handled")
                }
            }
        },
        Err(_) => panic!("will be handled")
    }
}

通常您在

main
中不会有太多逻辑,只需在另一个函数中使用
?
try!
即可:

use std::fs;
use std::path::Path;

fn print_filetimes(path: &Path) -> Result<(), std::io::Error> {
    for entry in fs::read_dir(&path)? {
        let time = entry.and_then(|e| e.metadata()).map(|m| m.accessed())?;
        println!("{:?}", time);
    }

    Ok(())
}

fn main() {
    let path = Path::new(".");
    match print_filetimes(path) {
        Ok(()) => (),
        Err(_) => panic!("will be handled"),
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.