目录

第十一章 自定义错误类型

11.1 为什么需要自定义错误

当程序变得复杂时,使用标准错误类型可能不足以表达具体的错误情况。自定义错误类型可以:

11.2 定义简单错误类型

使用枚举定义错误

#[derive(Debug)]
enum MathError {
    DivisionByZero,
    NegativeSquareRoot,
    Overflow,
}
 
fn divide(a: f64, b: f64) -> Result<f64, MathError> {
    if b == 0.0 {
        Err(MathError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}
 
fn sqrt(x: f64) -> Result<f64, MathError> {
    if x < 0.0 {
        Err(MathError::NegativeSquareRoot)
    } else {
        Ok(x.sqrt())
    }
}

11.3 实现Error trait

标准Error trait

为了让错误类型更完善,应该实现std::error::Error:

use std::error::Error;
use std::fmt;
 
#[derive(Debug)]
enum MathError {
    DivisionByZero,
    NegativeSquareRoot(f64),
    Overflow,
}
 
impl fmt::Display for MathError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MathError::DivisionByZero => {
                write!(f, "除数不能为零")
            }
            MathError::NegativeSquareRoot(x) => {
                write!(f, "不能对负数开平方根:{}", x)
            }
            MathError::Overflow => {
                write!(f, "数值溢出")
            }
        }
    }
}
 
impl Error for MathError {}

11.4 错误转换

From trait

使用From trait可以将其他错误转换为自定义错误:

use std::num::ParseIntError;
 
#[derive(Debug)]
enum AppError {
    ParseError(ParseIntError),
    IoError(std::io::Error),
    InvalidInput(String),
}
 
impl From<ParseIntError> for AppError {
    fn from(err: ParseIntError) -> AppError {
        AppError::ParseError(err)
    }
}
 
impl From<std::io::Error> for AppError {
    fn from(err: std::io::Error) -> AppError {
        AppError::IoError(err)
    }
}
 
fn parse_number(s: &str) -> Result<i32, AppError> {
    let num = s.parse()?;  // 自动转换
    Ok(num)
}

11.5 错误链

source方法

Error trait的source方法可以提供错误的底层原因:

use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
 
#[derive(Debug)]
struct ConfigError {
    message: String,
    source: Option<Box<dyn Error>>,
}
 
impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}
 
impl Error for ConfigError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref())
    }
}
 
fn load_config() -> Result<String, ConfigError> {
    let content = std::fs::read_to_string("config.txt")
        .map_err(|e| ConfigError {
            message: "无法读取配置文件".to_string(),
            source: Some(Box::new(e)),
        })?;
 
    Ok(content)
}

11.6 使用thiserror库

简化错误定义

thiserror库可以大幅减少样板代码:

use thiserror::Error;
 
#[derive(Error, Debug)]
enum DataStoreError {
    #[error("数据未找到")]
    NotFound,
 
    #[error("无效的头部:{0}")]
    InvalidHeader(String),
 
    #[error("数据库错误")]
    DatabaseError(#[from] sqlx::Error),
 
    #[error("IO错误")]
    IoError(#[from] std::io::Error),
}

11.7 使用anyhow库

简化错误处理

anyhow库适用于应用程序(而非库),简化错误处理:

use anyhow::{Context, Result};
 
fn get_cluster_info() -> Result<ClusterInfo> {
    let config = std::fs::read_to_string("cluster.json")
        .context("读取集群配置失败")?;
 
    let info: ClusterInfo = serde_json::from_str(&config)
        .context("解析集群配置失败")?;
 
    Ok(info)
}

anyhow特点:

11.8 自定义错误最佳实践

库与应用的区别

库(Library):

应用(Application):

完整示例:配置文件解析器

use std::error::Error;
use std::fmt;
use std::fs;
use std::num::ParseIntError;
 
// 自定义错误类型
#[derive(Debug)]
enum ConfigError {
    FileNotFound(String),
    ParseError { line: usize, source: ParseIntError },
    InvalidFormat(String),
}
 
impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ConfigError::FileNotFound(path) => {
                write!(f, "配置文件未找到:{}", path)
            }
            ConfigError::ParseError { line, source } => {
                write!(f, "第{}行解析失败:{}", line, source)
            }
            ConfigError::InvalidFormat(msg) => {
                write!(f, "格式错误:{}", msg)
            }
        }
    }
}
 
impl Error for ConfigError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            ConfigError::ParseError { source, .. } => Some(source),
            _ => None,
        }
    }
}
 
// 配置结构
#[derive(Debug)]
struct Config {
    values: Vec<i32>,
}
 
impl Config {
    fn from_file(path: &str) -> Result<Self, ConfigError> {
        let content = fs::read_to_string(path)
            .map_err(|_| ConfigError::FileNotFound(path.to_string()))?;
 
        let mut values = Vec::new();
 
        for (line_num, line) in content.lines().enumerate() {
            let line = line.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }
 
            let value: i32 = line.parse()
                .map_err(|e| ConfigError::ParseError {
                    line: line_num + 1,
                    source: e,
                })?;
 
            values.push(value);
        }
 
        if values.is_empty() {
            return Err(ConfigError::InvalidFormat(
                "配置文件至少需要一个数值".to_string()
            ));
        }
 
        Ok(Config { values })
    }
}
 
fn main() {
    match Config::from_file("config.txt") {
        Ok(config) => println!("配置加载成功:{:?}", config),
        Err(e) => {
            eprintln!("错误:{}", e);
            if let Some(source) = e.source() {
                eprintln!("原因:{}", source);
            }
        }
    }
}

练习题

练习题11.1:实现ParseError

use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
 
#[derive(Debug)]
enum MyError {
    EmptyInput,
    ParseFailed(ParseIntError),
}
 
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::EmptyInput => write!(f, "输入为空"),
            MyError::ParseFailed(e) => write!(f, "解析失败:{}", e),
        }
    }
}
 
impl Error for MyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            MyError::ParseFailed(e) => Some(e),
            _ => None,
        }
    }
}
 
impl From<ParseIntError> for MyError {
    fn from(err: ParseIntError) -> Self {
        MyError::ParseFailed(err)
    }
}
 
fn parse_numbers(input: &str) -> Result<Vec<i32>, MyError> {
    if input.is_empty() {
        return Err(MyError::EmptyInput);
    }
 
    input.split(',')
        .map(|s| s.trim().parse())
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.into())
}
 
fn main() {
    match parse_numbers("1, 2, 3") {
        Ok(nums) => println!("数字:{:?}", nums),
        Err(e) => {
            println!("错误:{}", e);
            if let Some(source) = e.source() {
                println!("原因:{}", source);
            }
        }
    }
}

本章小结

本章学习了自定义错误类型:

设计良好的错误类型对于库的可维护性和用户体验都很重要。