第4条:优先使用惯用的错误类型第3条描述了如何使用标准库为Option和Result类型提供的转换,以允许使用?运算符简洁、惯用地处理结果类型。但它没有讨论如何最好地处理作为Result<T,E>第二个类型参数出现的各种不同的错误类型E;这就是本章节的内容。只有当有多
[第 3 条]描述了如何使用标准库为 Option
和 Result
类型提供的转换,以允许使用 ?
运算符简洁、惯用地处理结果类型。但它没有讨论如何最好地处理作为 Result<T, E>
第二个类型参数出现的各种不同的错误类型 E
;这就是本章节的内容。
只有当有多种不同的错误类型时,这才有相关性。如果函数遇到的所有不同错误已经是同一类型,它可以只返回该类型。当有不同类型的错误时,需要做出一个决定,即是否保留子错误类型信息。
了解标准特征([第 10 条])总是一个好主意,这里相关的特征是 std::error::Error
。Result
的 E
类型参数不必是实现 Error
的类型,但这是一个常见的约定,它允许包装器表达适当的特征约束 —— 因此,最好为您的错误类型实现 Error
。
首先要注意的是,对于错误类型,唯一硬性要求是特征约束:实现 Error
的任何类型也必须实现以下特征:
Display
特征,意味着可以使用 {}
进行格式化
Debug
特征,意味着可以使用 {:?}
进行格式化
换句话说,应该能够将错误类型显示给用户和程序员。
特征中唯一的方法是 source()
,[^1] 它允许错误类型公开一个内部的、嵌套的错误。此方法是可选的 —— 它带有一个返回 None
的默认实现([第 13 条]),表示内部错误信息不可用。
最后要注意的一点是:如果您正在为 no_std
环境([第 33 条])编写代码,可能无法实现 Error
—— Error
特征目前在 std
中实现,而不是 core
,因此不可用。[^2]
如果不需要嵌套错误信息,那么错误类型的实现不必比 String
复杂多少 —— 这是一个“字符串类型”的变量可能合适的罕见情况。但它需要比 String
多一点;虽然可以使用 String
作为 E
类型参数:
pub fn find_user(username: &str) -> Result<UserId, String> {
let f = std::fs::File::open("/etc/passwd")
.map_err(|e| format!("Failed to open password file: {:?}", e))?;
// ...
}
一个 String
并不实现 Error
,我们希望是这样,以便代码的其他部分可以处理 Errors
。为 String
实现 Error
是不可能的,因为特征(trait
)和类型都不属于我们(所谓的孤儿规则):
impl std::error::Error for String {}
error[E0117]: only traits defined in the current crate can be implemented for
types defined outside of the crate
--> src/main.rs:18:5
|
18 | impl std::error::Error for String {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^------
| | |
| | `String` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
[类型别名]也无济于事,因为它并没有创建一个新的类型,所以也不会改变错误信息:
pub type MyError = String;
impl std::error::Error for MyError {}
error[E0117]: only traits defined in the current crate can be implemented for
types defined outside of the crate
--> src/main.rs:41:5
|
41 | impl std::error::Error for MyError {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^-------
| | |
| | `String` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead
像往常一样,编译器错误消息为解决问题提供了一个线索。定义一个包装 String
类型的元组结构体("新类型模式",[第 6 条])允许实现 Error
特征,前提是也实现了 Debug
和 Display
:
#[derive(Debug)]
pub struct MyError(String);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for MyError {}
pub fn find_user(username: &str) -> Result<UserId, MyError> {
let f = std::fs::File::open("/etc/passwd").map_err(|e| {
MyError(format!("Failed to open password file: {:?}", e))
})?;
// ...
}
为了方便起见,实现 From<String>
特征可能是有意义的,以便可以轻松地将字符串值转换为 MyError
实例([第 5 条]):
impl From<String> for MyError {
fn from(msg: String) -> Self {
Self(msg)
}
}
当编译器遇到问号运算符(?
)时,它会自动应用任何需要的 From
特征实现,以便达到目标错误返回类型。这允许进一步的最小化:
pub fn find_user(username: &str) -> Result<UserId, MyError> {
let f = std::fs::File::open("/etc/passwd")
.map_err(|e| format!("Failed to open password file: {:?}", e))?;
// ...
}
这里的错误路径涵盖了以下步骤:
File::open
返回一个类型为 std::io::Error
的错误。format!
使用 std::io::Error
的 Debug
实现将其转换为 String
。?
使编译器寻找并使用一个 From
实现,该实现可以将它从 String
转换为 MyError
。另一种情况是,嵌套错误的内容重要到足以需要被保留并供调用者使用。
考虑一个库函数,它尝试返回文件的第一行作为字符串,只要这一行不是太长。稍微思考一下就会发现(至少)三种可能发生的不同类型的失败:
UTF-8
的数据,因此无法转换为 String
。根据 [第 1 条],您可以使用类型系统来表达并包含所有这些可能性作为一个枚举
:
#[derive(Debug)]
pub enum MyError {
Io(std::io::Error),
Utf8(std::string::FromUtf8Error),
General(String),
}
这个枚举
定义包括了 derive(Debug)
,但为了满足 Error
特征,还需要一个 Display
s实现:
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::F...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!