rust中如何比较优雅的进行错误处理,这是一直以来困扰我的一个问题。最近写了一个ip地址库查询包,于是在其中实践了一下自定义错误、错误抛出等处理。
rust中,如果一个函数需要返回错误,那么应该用Result
包裹返回值,Result
定义如下
enum Result<T, E> {
Ok(T),
Err(E),
}
通常,Result
中的第二个参数就是错误。比如Result<String, String>
,那么它的错误就是一个String类型的值,如下
// 返回了正常值hi
fn find() -> Result<String, String> {
Ok("hi".to_string())
}
// 返回了错误信息error
fn find() -> Result<String, String> {
Err("error".to_string())
}
那么有另外一个问题,就是,我们如何处理其他库或者第三方函数抛出给我们的错误呢?我们可以用expect()
,unwrap()
来解决可以解决的错误,但是有时候我们不希望处理错误,希望能够将错误抛出给上层,让上层调用者去处理。这时候,我们应该怎么定义错误类型?你当然可以在你的函数中进行错误处理之后,抛出String
类型的错误,以便继续使用String
类型作为错误类型。
#[test]
fn test_find() {
let x = foo();
println!("{:?}", x.unwrap())
}
fn foo() -> Result<File, String> {
let x = bar();
match x {
Err(e) => Err(e.to_string()),
Ok(f) => Ok(f),
}
...
}
fn bar() -> Result<File, io::Error> {
File::open("regions.txt")
}
你可以看到,我在foo()
中调用bar()
,bar()
中返回了一个包含io::Error
的错误,而我的foo()
中要求返回的是String
,我在foo
中用match
处理了错误的情况,并且foo
中可能调用其他第三方库函数,返回的错误类型不尽相同,我每种错误类型都可以使用match
解开,然后返回String
类型的错误。但是这样处理起来,代码看起来就非常乱,到处都是match
错误。
其实我们有另一种方法,可以自己定义一个Error
类型,然后对Error
类型进行扩展,让它兼容其他类型,如下
use std::{fmt, io};
#[derive(Debug)]
pub enum Error {
ParseError,
ReadError,
InvalidIPError,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::ParseError => write!(f, "Parse Error"),
Error::ReadError => write!(f, "Read Error"),
Error::InvalidIPError => write!(f, "Invalid IP Error"),
}
}
}
impl From<io::Error> for Error {
fn from(_: io::Error) -> Self {
Error::ReadError
}
}
impl From<bincode::Error> for Error {
fn from(_: bincode::Error) -> Self {
Error::ParseError
}
}
impl From<std::net::AddrParseError> for Error {
fn from(_: std::net::AddrParseError) -> Self {
Error::InvalidIPError
}
}
我们的Error
类型,实现了fmt::Display
,让它能够处理我的包项目中可能出现的第三方错误,并且针对这些错误实现了各自的From<T>
的from
方法,这样,这些第三方错误就可以直接以我的Error
返回,使用?简写之后,代码就改成这样了
#[test]
fn test_find() {
let x = foo();
println!("{:?}", x.unwrap())
}
fn foo() -> Result<File, Error> {
let x = bar()?;
Ok(x)
}
fn bar() -> Result<File, io::Error> {
File::open("regions.txt")
}
对比可以看到,我在foo中处理bar抛出的错误变得简单了,直接一个问号就将可能得错误抛出到Error
了,然后再将Ok(x)
返回。这样一来,错误的抛出和处理就变得优雅多了。