找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

150

积分

0

好友

18

主题
发表于 3 天前 | 查看: 16| 回复: 0

在Rust开发中,错误处理是每个开发者必须掌握的技能。作为一种高效的系统编程语言,Rust的错误处理机制独具特色。thiserror库通过宏简化了自定义错误类型的定义,有效解决了手写Display和From实现的样板代码问题。

入门示例

通过一个完整的示例快速理解thiserror的基本用法:

项目结构

thiserror-demo/
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml配置

[package]
name = "thiserror-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
thiserror = "2"
anyhow = "1"

核心代码实现

use std::fs::File;
use std::io::{self, Read};
use thiserror::Error;

/// 自定义错误类型
#[derive(Debug, Error)]
enum AppError {
    /// IO错误自动转换
    #[error("IO错误: {0}")]
    Io(#[from] io::Error),

    /// 自定义错误
    #[error("配置格式错误: {0}")]
    InvalidConfig(String),

    /// anyhow用于兜底错误
    #[error("内部错误: {0}")]
    Internal(#[from] anyhow::Error),
}

/// 读取配置文件
fn read_config(path: &str) -> Result<String, AppError> {
    let mut f = File::open(path)?;  // 自动转成AppError::Io
    let mut s = String::new();
    f.read_to_string(&mut s)?;

    if !s.starts_with("version") {
        return Err(AppError::InvalidConfig("缺少version字段".into()));
    }

    Ok(s)
}

fn main() -> Result<(), AppError> {
    match read_config("config.txt") {
        Ok(content) => println!("读取成功:\n{content}"),
        Err(err) => println!("发生错误: {err}"),
    }
    Ok(())
}

运行结果:

发生错误: IO错误: No such file or directory (os error 2)

核心API详解

1. 错误定义与显示

#[derive(Error)]#[error(...)]宏组合使用,自动实现std::error::Error trait并定义Display输出:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("IO错误: {0}")]
    Io(std::io::Error),

    #[error("自定义错误: {msg}")]
    Custom { msg: String },
}

fn demo_io_error() -> Result<(), MyError> {
    let _ = std::fs::read_to_string("不存在的文件.txt")
        .map_err(MyError::Io)?;
    Ok(())
}

fn demo_custom_error() -> Result<(), MyError> {
    Err(MyError::Custom { msg: "非法状态".into() })
}

fn main() {
    println!("{}", demo_io_error().unwrap_err());  // 调用Display
    println!("{:?}", demo_io_error().unwrap_err()); // 调用Debug
}

输出结果:

IO错误: No such file or directory (os error 2)
Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })

2. 自动错误转换

#[from]属性自动实现From trait,支持?操作符链式调用:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("解析失败: {0}")]
    Parse(#[from] std::num::ParseIntError),
}

fn demo_from() -> Result<i32, AppError> {
    let s = "abc";
    let n: i32 = s.parse()?;  // 自动转换ParseIntError到AppError
    Ok(n)
}

3. 错误链追踪

#[source]属性标记底层错误源,构建完整的错误链:

use thiserror::Error;
use std::io;

#[derive(Debug, Error)]
pub enum DbError {
    #[error("数据库查询失败: {query}")]
    Query {
        query: String,
        #[source]
        source: io::Error,
    },
}

#[derive(Debug, Error)]
pub enum AppError {
    #[error("业务处理失败: {0}")]
    Db(#[from] DbError),
}

fn db_layer() -> Result<(), DbError> {
    let io_err = io::Error::new(io::ErrorKind::NotFound, "文件不存在");
    Err(DbError::Query { 
        query: "SELECT *".into(), 
        source: io_err 
    })
}

fn main() {
    let err = db_layer().unwrap_err();
    println!("Display: {}", err);

    // 遍历错误链
    let mut source = err.source();
    let mut level = 1;
    while let Some(s) = source {
        println!("Source level {}: {}", level, s);
        source = s.source();
        level += 1;
    }
}

4. 透明错误传递

#[error(transparent)]直接传递底层错误,不添加额外信息:

use thiserror::Error;
use std::io;

#[derive(Debug, Error)]
pub enum MyError {
    #[error(transparent)]
    Io(#[from] io::Error),
}

fn read_file() -> Result<String, MyError> {
    let content = std::fs::read_to_string("不存在.txt")?;
    Ok(content)
}

高级应用技巧

1. 错误包装器模式

统一管理多种错误类型:

#[derive(Error, Debug)]
pub enum AppError {
    #[error(transparent)]
    Db(#[from] sqlx::Error),

    #[error(transparent)]
    Anyhow(#[from] anyhow::Error),

    #[error("业务错误: {0}")]
    Biz(String),
}

2. 上下文错误处理

结合业务上下文提供更详细的错误信息:

use thiserror::Error;
use std::path::PathBuf;
use std::fs::File;
use std::io;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("读取配置文件 '{path}' 失败")]
    ReadFailed {
        path: PathBuf,
        #[source]
        source: io::Error,
    },
}

fn load_config(path: PathBuf) -> Result<(), ConfigError> {
    File::open(&path).map_err(|e| ConfigError::ReadFailed {
        path: path.clone(),
        source: e,
    })?;
    Ok(())
}

3. 分层错误设计

模块化错误定义,提高代码可维护性:

errors/
    ├─ mod.rs
    ├─ io_error.rs
    ├─ service_error.rs
    └─ db_error.rs

统一包装:

#[derive(Error, Debug)]
pub enum AppError {
    #[error(transparent)]
    Io(#[from] IoError),

    #[error(transparent)]
    Db(#[from] DbError),
}

与anyhow的协同使用

thiserror负责定义结构化错误,anyhow处理应用层错误:

use thiserror::Error;
use anyhow::{Context, Result};

// 底层库代码使用thiserror
#[derive(Error, Debug)]
pub enum DbError {
    #[error("连接数据库失败")]
    ConnectionFailed,
}

fn query_user_from_db(id: u32) -> std::result::Result<String, DbError> {
    if id == 0 {
        return Err(DbError::ConnectionFailed);
    }
    Ok("Alice".to_string())
}

// 应用层使用anyhow处理错误
fn handle_request(user_id: u32) -> Result<String> {
    let user = query_user_from_db(user_id)
        .context(format!("处理用户请求ID:{}失败", user_id))?;
    Ok(user)
}

总结

thiserror库为Rust错误处理提供了强大的宏支持,显著减少了样板代码。结合anyhow使用,可以在库层保持错误类型的具体性,在应用层获得灵活的错误处理能力。这种组合已成为Rust项目错误处理的最佳实践方案。

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-1 13:29 , Processed in 0.054355 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表