Rust 错误处理的黄金搭档(thiserror和anyhow)

 更新时间:2026年06月17日 10:04:25   作者:Rust研习社  
本文主要介绍了Rust 错误处理的黄金搭档(thiserror和anyhow),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在 Rust 开发中,错误处理是不可或缺的核心环节,但手动实现错误相关 trait 往往会产生大量的样板代码。而为了解决这些样板代码,那就不得不提到两个主流的 Rust 错误处理库 thiserroranyhow 了,现在,我们一起来看看它们解决了什么样的问题。

简化自定义错误

在其他语言中,自定义错误非常简单,以 Go 为例,只需要实现 error 接口即可:

import "fmt"

type NotFoundError struct {
    Resource string
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("%s not found", e.Resource)
}

而在 Rust 中,自定义错误就比较麻烦了,需要手动实现 Error、Display 和 Debug 这三个特征。如果需要支持错误自动转换,即适配 ? 操作符,还需要额外实现 From 特征:

use std::error::Error;
use std::{fmt, io, num::ParseIntError};

#[derive(Debug)]
enum MyError {
    Io(io::Error),
    Parse(ParseIntError),
    Custom(String),
}

// 实现 Display 特征
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(s) => write!(f, "自定义错误: {}", s),
        }
    }
}

// 实现 Error 特征,标记该类型为错误类型
impl Error for MyError {}

// 实现 From 特征,支持错误自动转换
impl From<io::Error> for MyError {
    fn from(e: io::Error) -> Self {
        MyError::Io(e)
    }
}

impl From<ParseIntError> for MyError {
    fn from(e: ParseIntError) -> Self {
        MyError::Parse(e)
    }
}

可以看到,仅是定义一个简单的自定义错误枚举,就需要编写大量的样板代码。而 thiserror 库则通过宏解决了这一问题。它能在编译期自动生成上述所有样板代码,让我们专注于错误本身的定义。

我们用 thiserror 来改写上面的示例,实现如下:

use std::{io, num::ParseIntError};
use thiserror::Error;

#[derive(Error, Debug)] 
enum MyError {
    // 定义 Display 输出格式,{0} 引用变体第一个字段
    #[error("IO 错误: {0}")]
    // #[from] 自动实现 From 特征
    Io(#[from] io::Error),

    #[error("解析错误: {0}")]
    Parse(#[from] ParseIntError),

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

需要注意的是,thiserror 是基于宏实现,所有代码生成都在编译期完成,运行时零开销,不会对程序性能带来影响。因此,在需要自定义错误类型的场景中,可以大胆的使用 thiserror。

简化通用错误处理

在实际开发中,我们常常会遇到函数返回多种不同错误类型的场景。此时有两种处理思路:一是定义全局自定义错误枚举,也就是上一章节的方案,可以通过 thiserror 简化。二是使用 Box<dyn Error> 作为错误返回类型,无需单独定义错误枚举。

use std::error::Error;
use std::fs;

fn read_and_parse(path: &str) -> Result<i32, Box<dyn Error>> {
    let content = fs::read_to_string(path)?;
    let num: i32 = content.trim().parse()?;
    Ok(num)
}

先简单了解下 Box<dyn Error>,由于所有的错误类型都实现 Error 特征,dyn Error 作为动态派发的特征对象,所以可以兼容所有错误类型。

然而由于 dyn Error 是不定长类型(DST),无法直接存储在栈上,因此需要用 Box<T> 智能指针将其分配到堆上。

最后是标准库中已为所有实现 Error 特征的类型实现了 From 转换,因此可以直接使用 ? 操作符自动转换错误类型。标准库实现大致如下:

impl<E: Error + 'static> From<E> for Box<dyn Error> {
    fn from(err: E) -> Self {
        Box::new(err)
    }
}

搞懂了 Box<dyn Error> 后,现在我们就可以讲 anyhow 了,anyhow 的底层实现是:

Box<dyn Error + Send + Sync + 'static>

不难看出,anyhow 是 Box<dyn Error> 的增强版,具体如下:

简洁的类型别名

anyhow 提供 anyhow::Result<T> 类型别名,简化代码:

use anyhow::Result;

fn main() -> Result<()> {
    Ok(())
}

携带业务上下文

anyhow 内置的 context 和 with_context 方法,它们为错误添加上下文:

use anyhow::{Context, Result};
use std::fs;

fn read_and_parse(path: &str) -> Result<i32> {
    let content = fs::read_to_string(path)?;
    let num: i32 = content.trim().parse()?;
    Ok(num)
}

fn main() -> anyhow::Result<()> {
    let content = fs::read_to_string("config.json")
        .context("读取配置文件失败")?;
    
    println!("{}", content);
    
    Ok(())
}

// Output:
// Error: 读取配置文件失败

// Caused by:
//     No such file or directory (os error 2)

支持多线程与异步

Box<dyn Error> 未约束 Send + Sync,所以在多线程或异步场景下时会直接报错。而 anyhow 强制约束了 Send + Sync,开箱即用,完全兼容多线程与异步场景:

use std::thread;

fn do_work() -> anyhow::Result<()> {
    Err(anyhow::anyhow!("something wrong"))
}

fn main() {
    let handle = thread::spawn(|| do_work());

    let result = handle.join().unwrap();

    match result {
        Ok(_) => println!("success"),
        Err(e) => println!("error: {:#}", e),
    }
}

// Output:
// error: something wrong

结语

在这篇文章中,我们介绍了 thiserror 和 anyhow 的使用方法,同时讲解了它们的设计初衷,解决 Rust 原生错误处理的哪些痛点。学习技术时,知其然更要知其所以然,理解它们为什么存在,才能在实际开发中灵活运用,选择最适合的方案。

到此这篇关于Rust 错误处理的黄金搭档(thiserror和anyhow)的文章就介绍到这了,更多相关Rust 错误处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Rust开发环境搭建到运行第一个程序HelloRust的图文教程

    Rust开发环境搭建到运行第一个程序HelloRust的图文教程

    本文主要介绍了Rust开发环境搭建到运行第一个程序HelloRust的图文教程,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-12-12
  • 前端基于Rust实现的Wasm进行图片压缩的技术文档(实现方案)

    前端基于Rust实现的Wasm进行图片压缩的技术文档(实现方案)

    在现代Web开发中,利用Rust编写的图片压缩代码可以编译成WebAssembly(Wasm)模块,Rust的内存安全特性和Wasm的跨平台能力,使得这种方案既高效又安全,对Rust Wasm图片压缩实现方案感兴趣的朋友一起看看吧
    2024-09-09
  • Rust安装的图文教程

    Rust安装的图文教程

    本文主要介绍了Rust安装的图文教程,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • Rust中箱、包和模块的学习笔记

    Rust中箱、包和模块的学习笔记

    Rust中有三个重要的组织概念:箱、包、模块,本文主要介绍了Rust中箱、包和模块的学习笔记,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • rust文件读写的实现示例

    rust文件读写的实现示例

    Rust语言提供了强大的文件读写库,使得开发者可以更加方便地进行文件操作,并且其安全性可以有效避免文件操作中可能出现的风险,本文就来详细的介绍了rust文件读写的实现示例,感兴趣的可以了解一下
    2023-12-12
  • Tauri 打开本地文件踩坑分析解决

    Tauri 打开本地文件踩坑分析解决

    这篇文章主要为大家介绍了Tauri 打开本地文件踩坑分析解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • rust将bitmap位图文件另存为png格式的方法

    rust将bitmap位图文件另存为png格式的方法

    通过添加依赖,转换函数和单元测试操作步骤来解决将bitmap位图文件另存为png格式文件,本文通过实例代码给大家介绍的非常详细,对rust bitmap位另存为png格式的操作方法感兴趣的朋友一起看看吧
    2024-03-03
  • Rust搭建webserver的底层原理与应用实战技巧

    Rust搭建webserver的底层原理与应用实战技巧

    本文介绍Rust在HTTP编程中的应用,涵盖协议基础、标准库与第三方库(如hyper、reqwest)的使用,以及多线程服务器和线程池的实现与关闭机制,展示Rust在性能、安全和并发方面的优势,感兴趣的朋友跟随小编一起看看吧
    2025-06-06
  • 教你使用RustDesk 搭建一个自己的远程桌面中继服务器

    教你使用RustDesk 搭建一个自己的远程桌面中继服务器

    这篇文章主要介绍了RustDesk 搭建一个自己的远程桌面中继服务器,主要包括服务端安装和客户端配置方法,配置好相关操作输入控制码即可发起远程或文件传输,本文通过图文给大家讲解的非常详细,需要的朋友可以参考下
    2022-08-08
  • Rust 数据分析利器polars用法详解

    Rust 数据分析利器polars用法详解

    这篇文章主要介绍了Rust 数据分析利器polars用法详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-08-08

最新评论