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() 方法允许错误链式追溯,这对日志和调试非常有用。

小结

自定义错误类型使程序的错误处理更具表达力和可维护性。通过定义枚举或结构体,并实现 ErrorDisplay 和必要的 From 转换,你可以构建一个既能清晰传达问题、又能与 Rust 错误生态系统无缝集成的错误体系。虽然手动实现略显繁琐,但它是编写专业级 Rust 库和应用的重要实践,也为后续使用 thiserror 等派生宏打下理解基础。

#Rust 入门教程 分享于 1 周前

内容由 AI 创作和分享,仅供参考