7.4 自定义错误类型
听
在小型程序中,直接使用标准库的错误类型(如 std::io::Error)可能足够。但在中大型项目中,为了提供清晰的错误信息、支持分层错误处理以及便于调试,通常需要定义自定义错误类型。Rust 鼓励通过组合和实现标准 trait 来构建结构化、可组合的错误体系。
定义枚举错误类型
最常见的做法是使用枚举表示多种可能的错误:
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
InvalidInput(String),
}
每个变体对应一种错误来源。为了让这个类型能被当作通用错误使用,应实现 std::error::Error trait。
实现 Error、Display 和 Debug
std::error::Error trait 要求同时实现 Display(用于用户可读信息)和 Debug(用于开发者诊断)。通常用 #[derive(Debug)] 自动生成 Debug,手动实现 Display:
use std::fmt;
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "I/O error: {}", e),
AppError::Parse(e) => write!(f, "Parse error: {}", e),
AppError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
}
}
}
impl std::error::Error for AppError {}
现在 AppError 可以被任何接受 dyn Error 的代码处理。
实现 From 以支持 ? 自动转换
为了让 ? 能自动将底层错误(如 io::Error)转换为 AppError,需为 AppError 实现 From trait:
impl From<std::io::Error> for AppError {
fn from(error: std::io::Error) -> Self {
AppError::Io(error)
}
}
impl From<std::num::ParseIntError> for AppError {
fn from(error: std::num::ParseIntError) -> Self {
AppError::Parse(error)
}
}
有了这些实现,以下代码就能正常工作:
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
let contents = std::fs::read_to_string(path)?; // 自动转为 AppError::Io
let num = contents.trim().parse()?; // 自动转为 AppError::Parse
if num < 0 {
return Err(AppError::InvalidInput("Number must be non-negative".into()));
}
Ok(num)
}
? 在遇到 Result<T, E> 时,会尝试通过 From<E> for YourError 进行转换。若存在对应 From 实现,则自动完成错误包装。
使用结构体组织复杂错误
对于需要携带更多上下文的场景,也可使用结构体:
#[derive(Debug)]
struct ConfigError {
file: String,
source: std::io::Error,
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Failed to load config from {}: {}", self.file, self.source)
}
}
impl std::error::Error for ConfigError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
注意:source() 方法允许错误链式追溯,这对日志和调试非常有用。
小结
自定义错误类型使程序的错误处理更具表达力和可维护性。通过定义枚举或结构体,并实现 Error、Display 和必要的 From 转换,你可以构建一个既能清晰传达问题、又能与 Rust 错误生态系统无缝集成的错误体系。虽然手动实现略显繁琐,但它是编写专业级 Rust 库和应用的重要实践,也为后续使用 thiserror 等派生宏打下理解基础。
#Rust 入门教程
分享于 1 周前
上一篇:7.3 ? 操作符简化错误传播
下一篇:7.5 错误处理最佳实践与第三方库