有时通过将所有不同类型的错误标志为单一的错误类型来简化代码。下面我们将展示如何自定义一个错误类型。
- Rust允许用户自定义错误类型。通常一个好的错误类型有一些特征:
- 使用相同的类型代表不同的错误
- 向用户展示友好的错误信息
- 容易和其他错误类型进行比较:
- 好的:Err(EmptyVec)
- 坏的:Err("Please use a vector with at least one element".to_owned())
- 和其他错误类型组合容易
use std::fmt;
type Result<T> = std::result::Result<T, DoubleError>;
// Define our error types. These may be customized for our error handling cases.
// Now we will be able to write our own errors, defer to an underlying error
// implementation, or do something in between.
#[derive(Debug, Clone)]
struct DoubleError;
// Generation of an error is completely separate from how it is displayed.
// There's no need to be concerned about cluttering complex logic with the display style.
//
// Note that we don't store any extra info about the errors. This means we can't state
// which string failed to parse without modifying our types to carry that information.
impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}
fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
// Change the error to our new type.
.ok_or(DoubleError)
.and_then(|s| {
s.parse::<i32>()
// Update to the new error type here also.
.map_err(|_| DoubleError)
.map(|i| 2 * i)
})
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];
print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}
内容解析
为什么要自定义错误类型
在复杂的程序中,函数可能遇到多种不同的错误。使用自定义错误类型可以:
- 统一错误处理:将多种不同的错误类型封装成一种类型,简化函数签名和错误处理逻辑。
- 提供更好的信息:可以在错误类型中携带具体的错误信息(如错误值,位置等),而不仅仅是字符串。
- 提升可读性和可维护性:有明确类型的错误比字符串错误更容易比较、匹配和调试。
- 更好的组合性:自定义错误类型可以更容易地与其他错误类型交互和转换。
什么事好的错误类型
良好错误类型的特征:
- 统一表示:用同一类型表示不同错误
- 友好信息:为用户提供清晰的错误消息
- 易于比较:Err(EmptyVec)比Err(“message”.to_owned())更好
- 携带信息:能够保存错误相关的上下文信息
- 组合性好:能与其他错误类型良好协作
代码优缺点分析
优点:
- 统一错误处理:所有可能的错误都转换为统一的DoubleError类型
- 简化接口:函数返回简单的Result<i32>,DoubleError>而不是复杂的嵌套类型
- 关注点分离:错误生成逻辑与显示逻辑分离(通过实现Displaytrait)
局限性:
- 信息丢失:当前的DoubleError是空结构体,无法区分是空向量错误还是解析失败的错误。
- 缺乏细节:不知道具体是哪个字符串解析失败,也不知道失败原因。
改进方向
在实际项目中,通常会定义更加丰富的错误类型:
#[derive(Debug, Clone)]
enum DoubleError {
EmptyVec,
ParseError(String, std::num::ParseIntError),
}
impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DoubleError::EmptyVec => write!(f, "please use a vector with at least one element"),
DoubleError::ParseError(s, e) => write!(f, "failed to parse '{}': {}", s, e),
}
}
}
总结
这段代码展示了Rust中自定义错误类型的基本模式:
定义自定义错误类型(结构体或枚举)
实现Displaytrait来定义错误显示方式
使用ok_or和map_err将不同的错误类型转化为统一的自定义错误类型
通过类型别名简化为Result的书写方式
这种模式提供了比简单使用字符串错误更强大、类型更安全的错误处理机制,是构建健壮Rust应用程序的基础。虽然示例中的实际比较简单,但它展示了自定义错误类型的核心概念和优势。