====== 第十一章 自定义错误类型 ====== ===== 11.1 为什么需要自定义错误 ===== 当程序变得复杂时,使用标准错误类型可能不足以表达具体的错误情况。自定义错误类型可以: * 提供更有意义的错误信息 * 区分不同类型的错误 * 实现错误转换和链式处理 ===== 11.2 定义简单错误类型 ===== ==== 使用枚举定义错误 ==== #[derive(Debug)] enum MathError { DivisionByZero, NegativeSquareRoot, Overflow, } fn divide(a: f64, b: f64) -> Result { if b == 0.0 { Err(MathError::DivisionByZero) } else { Ok(a / b) } } fn sqrt(x: f64) -> Result { 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 for AppError { fn from(err: ParseIntError) -> AppError { AppError::ParseError(err) } } impl From for AppError { fn from(err: std::io::Error) -> AppError { AppError::IoError(err) } } fn parse_number(s: &str) -> Result { 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>, } 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 { 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 { let config = std::fs::read_to_string("cluster.json") .context("读取集群配置失败")?; let info: ClusterInfo = serde_json::from_str(&config) .context("解析集群配置失败")?; Ok(info) } **anyhow特点:** * 自动实现错误转换 * 方便的Context添加错误信息 * 适用于应用开发 ===== 11.8 自定义错误最佳实践 ===== ==== 库与应用的区别 ==== **库(Library):** * 使用自定义错误类型 * 实现Error trait * 考虑使用thiserror * 提供详细的错误信息 **应用(Application):** * 可以使用anyhow简化 * 关注用户体验的错误信息 * 错误处理和报告 ==== 完整示例:配置文件解析器 ==== 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, } impl Config { fn from_file(path: &str) -> Result { 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 for MyError { fn from(err: ParseIntError) -> Self { MyError::ParseFailed(err) } } fn parse_numbers(input: &str) -> Result, MyError> { if input.is_empty() { return Err(MyError::EmptyInput); } input.split(',') .map(|s| s.trim().parse()) .collect::, _>>() .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); } } } } ===== 本章小结 ===== 本章学习了自定义错误类型: * **枚举定义错误**:不同的变体表示不同错误情况 * **Error trait**:实现Display和Error trait * **From trait**:错误自动转换 * **source方法**:错误链追踪 * **thiserror**:简化错误定义 * **anyhow**:简化应用错误处理 设计良好的错误类型对于库的可维护性和用户体验都很重要。