自定义错误类型
> 学会创建自定义错误类型,实现 Error trait,掌握 thiserror 库简化错误定义的方法。
自定义错误类型
为什么需要自定义错误?
rust
▶ Run// 问题:使用 Box<dyn Error>丢失了类型信息
fn process() -> Result<(), Box<dyn std::error::Error>> {
// 调用者无法知道可能返回什么错误
// 无法针对性地处理特定错误
}
// 解决:定义明确的错误类型
#[derive(Debug)]
enum AppError {
DatabaseError(String),
ValidationError(String),
NotFound(String),
}
fn process() -> Result<(), AppError> {
// 调用者清楚地知道可能的错误
// 可以针对性处理
}实现 Error trait
rust
▶ Runuse std::fmt;
use std::error::Error;
use std::io;
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(std::num::ParseIntError),
Custom(String),
}
// 实现 Display trait(必须)
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::Io(e) => write!(f, "IO 错误:{}", e),
MyError::Parse(e) => write!(f, "解析错误:{}", e),
MyError::Custom(msg) => write!(f, "自定义错误:{}", msg),
}
}
}
// 实现 Error trait
impl std::error::Error for MyError {
// 可选:提供错误源
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MyError::Io(e) => Some(e),
MyError::Parse(e) => Some(e),
MyError::Custom(_) => None,
}
}
}
// 实现 From trait,方便使用?
impl From<io::Error> for MyError {
fn from(err: io::Error) -> MyError {
MyError::Io(err)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(err: std::num::ParseIntError) -> MyError {
MyError::Parse(err)
}
}
fn main() -> Result<(), MyError> {
// 现在可以使用?自动转换错误
let _file = std::fs::File::open("hello.txt")?; // io::Error → MyError
let _num: i32 = "abc".parse()?; // ParseIntError → MyError
Ok(())
}使用 thiserror 简化
toml
# Cargo.toml
[dependencies]
thiserror = "1.0"rust
▶ Runuse thiserror::Error;
// thiserror 自动生成 Display 和 Error 实现
#[derive(Error, Debug)]
enum AppError {
// 简单错误消息
#[error("文件未找到:{0}")]
FileNotFound(String),
// 包装其他错误
#[error("IO 错误")]
Io(#[from] std::io::Error),
// 包装其他错误
#[error("解析错误")]
Parse(#[from] std::num::ParseIntError),
// 命名字段
#[error("配置无效:{message}")]
InvalidConfig { message: String },
// 错误链
#[error("处理数据失败")]
ProcessData {
source: std::io::Error,
},
// 多个错误源
#[error("操作失败:{0}")]
OperationFailed(#[source] Box<dyn Error + Send + Sync>),
}
fn main() -> Result<(), AppError> {
// 自动错误转换
let _file = std::fs::File::open("hello.txt")?;
// 创建错误
Err(AppError::FileNotFound("config.txt".to_string()))?;
Ok(())
}完整示例:用户注册系统
rust
▶ Runuse std::fmt;
use std::error::Error;
use thiserror::Error;
// 定义错误类型
#[derive(Error, Debug)]
enum RegistrationError {
#[error("用户名不能为空")]
EmptyUsername,
#[error("用户名太短(至少 3 个字符)")]
UsernameTooShort,
#[error("用户名已存在:{0}")]
UsernameTaken(String),
#[error("邮箱格式无效:{0}")]
InvalidEmail(String),
#[error("密码太弱(至少 8 个字符)")]
WeakPassword,
#[error("数据库错误:{0}")]
Database(#[from] DatabaseError),
}
#[derive(Error, Debug)]
enum DatabaseError {
#[error("连接失败:{0}")]
Connection(String),
#[error("查询失败:{0}")]
Query(String),
}
// 用户结构
#[derive(Debug)]
struct User {
username: String,
email: String,
}
// 验证函数
fn validate_username(username: &str) -> Result<(), RegistrationError> {
if username.is_empty() {
return Err(RegistrationError::EmptyUsername);
}
if username.len() < 3 {
return Err(RegistrationError::UsernameTooShort);
}
// 模拟检查用户名是否存在
if username == "admin" {
return Err(RegistrationError::UsernameTaken(username.to_string()));
}
Ok(())
}
fn validate_email(email: &str) -> Result<(), RegistrationError> {
if !email.contains('@') || !email.contains('.') {
return Err(RegistrationError::InvalidEmail(email.to_string()));
}
Ok(())
}
fn validate_password(password: &str) -> Result<(), RegistrationError> {
if password.len() < 8 {
return Err(RegistrationError::WeakPassword);
}
Ok(())
}
// 注册函数
fn register(username: &str, email: &str, password: &str)
-> Result<User, RegistrationError>
{
validate_username(username)?;
validate_email(email)?;
validate_password(password)?;
// 创建用户
Ok(User {
username: username.to_string(),
email: email.to_string(),
})
}
fn main() {
// 测试不同情况
let test_cases = vec![
("", "test@example.com", "password123"), // 空用户名
("ab", "test@example.com", "password123"), // 用户名太短
("admin", "test@example.com", "password123"), // 用户名已存在
("john", "invalid-email", "password123"), // 邮箱无效
("john", "john@example.com", "weak"), // 密码太弱
("john", "john@example.com", "password123"), // 成功
];
for (username, email, password) in test_cases {
match register(username, email, password) {
Ok(user) => println!("✓ 用户 {} 注册成功", user.username),
Err(e) => println!("✗ 注册失败:{}", e),
}
}
}输出:
✗ 注册失败:用户名不能为空
✗ 注册失败:用户名太短(至少 3 个字符)
✗ 注册失败:用户名已存在:admin
✗ 注册失败:邮箱格式无效:invalid-email
✗ 注册失败:密码太弱(至少 8 个字符)
✓ 用户 john 注册成功Result 的常用方法
rust
▶ Runfn main() {
// ========== 基本查询 ==========
let ok_result: Result<i32, &str> = Ok(5);
let err_result: Result<i32, &str> = Err("错误");
// is_ok / is_err
println!("{}", ok_result.is_ok()); // true
println!("{}", err_result.is_err()); // true
// ok / err - 转换为 Option
println!("{:?}", ok_result.ok()); // Some(5)
println!("{:?}", err_result.ok()); // None
println!("{:?}", ok_result.err()); // None
println!("{:?}", err_result.err()); // Some("错误")
// ========== 获取值 ==========
// unwrap - 获取值,错误则 panic
println!("{}", ok_result.unwrap()); // 5
// println!("{}", err_result.unwrap()); // panic!
// unwrap_or - 提供默认值
println!("{}", ok_result.unwrap_or(0)); // 5
println!("{}", err_result.unwrap_or(0)); // 0
// unwrap_or_else - 使用闭包
println!("{}", ok_result.unwrap_or_else(|_| 42));
println!("{}", err_result.unwrap_or_else(|e| {
println!("错误:{}", e);
42
}));
// unwrap_or_default - 使用默认值
println!("{:?}", err_result.unwrap_or_default()); // 0
// expect / expect_err - 自定义 panic 消息
// err_result.expect("自定义错误消息");
// ========== 转换值 ==========
// map - 转换 Ok 值
let squared = ok_result.map(|x| x * x);
println!("{:?}", squared); // Ok(25)
// map_or / map_or_else - 映射或默认值
println!("{}", ok_result.map_or(0, |x| x * 2)); // 10
println!("{}", err_result.map_or(0, |x| x * 2)); // 0
// map_err - 转换错误
let mapped = err_result.map_err(|e| format!("错误:{}", e));
println!("{:?}", mapped); // Err("错误:错误")
// ========== 链式操作 ==========
// and - 两个都 Ok 才返回第二个
let r1: Result<i32, &str> = Ok(5);
let r2: Result<i32, &str> = Ok(10);
println!("{:?}", r1.and(r2)); // Ok(10)
// and_then - 链式绑定
let result: Result<i32, &str> = Ok(5);
let chained = result.and_then(|x| Ok(x * 2));
println!("{:?}", chained); // Ok(10)
// or - 第一个 Err 才返回第二个
let r1: Result<i32, &str> = Err("错误 1");
let r2: Result<i32, &str> = Ok(10);
println!("{:?}", r1.or(r2)); // Ok(10)
// or_else - 错误时调用闭包
let result: Result<i32, &str> = Err("错误");
let fallback = result.or_else(|e| Ok(0));
println!("{:?}", fallback); // Ok(0)
// ========== 迭代器方法 ==========
let results = vec![Ok(1), Err("错"), Ok(3), Err("误")];
// collect - 收集 Result
let collected: Result<Vec<_>, _> = results.iter().cloned().collect();
println!("{:?}", collected); // Err("错")
// filter_map
let values: Vec<i32> = results
.into_iter()
.filter_map(|r| r.ok())
.collect();
println!("{:?}", values); // [1, 3](只保留 Ok)
}
---
## 小结
- 自定义错误类型可以提供明确的错误信息和类型安全
- 实现 Error trait 需要 Display 和 Debug trait
- 使用 `From` trait 可以自动转换错误,方便使用 `?`
- thiserror 库简化了错误定义,推荐在生产代码中使用
- Result 的常用方法包括 map、and_then、or_else 等
## 练习题
详见:[练习题](../../exercises/14-error-handling)