Skip to content

自定义错误类型

> 学会创建自定义错误类型,实现 Error trait,掌握 thiserror 库简化错误定义的方法。

自定义错误类型

为什么需要自定义错误?

rust
// 问题:使用 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> {
    // 调用者清楚地知道可能的错误
    // 可以针对性处理
}
▶ Run

实现 Error trait

rust
use 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(())
}
▶ Run

使用 thiserror 简化

toml
# Cargo.toml
[dependencies]
thiserror = "1.0"
rust
use 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(())
}
▶ Run

完整示例:用户注册系统

rust
use 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),
        }
    }
}
▶ Run

输出:

✗ 注册失败:用户名不能为空
✗ 注册失败:用户名太短(至少 3 个字符)
✗ 注册失败:用户名已存在:admin
✗ 注册失败:邮箱格式无效:invalid-email
✗ 注册失败:密码太弱(至少 8 个字符)
✓ 用户 john 注册成功

Result 的常用方法

rust
fn 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 需要 DisplayDebug trait
- 使用 `From` trait 可以自动转换错误,方便使用 `?`
- thiserror 库简化了错误定义,推荐在生产代码中使用
- Result 的常用方法包括 map、and_then、or_else 等

## 练习题

详见:[练习题](../../exercises/14-error-handling)
▶ Run