====== 第十一章 自定义错误类型 ======
===== 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**:简化应用错误处理
设计良好的错误类型对于库的可维护性和用户体验都很重要。