第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... 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!