当程序变得复杂时,使用标准错误类型可能不足以表达具体的错误情况。自定义错误类型可以:
#[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()) } }
为了让错误类型更完善,应该实现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 {}
使用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) }
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) }
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), }
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特点:
库(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); } } } }
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); } } } }
本章学习了自定义错误类型:
设计良好的错误类型对于库的可维护性和用户体验都很重要。