当前位置: 首页 > article >正文

Rust 错误处理库: thiserror 和 anyerror

在这篇博文中,我们将探索在Rust中使用两个流行的库来简化错误处理的策略:thiserror和anyway。我们将讨论它们的特性、用例,并提供关于何时选择每个库的见解。

需求提出

让我们首先创建函数decode()来进行说明。该功能有3个步骤:

  1. 从名为input的文件中读取内容
  2. 将每行解码为base64字符串
  3. 输出打印解码后的字符串

挑战在于确定decode的返回类型,因为std::fs::read_to_string() 、base64 decode() 和String::from_utf8() 各自返回不同的错误类型。

use base64::{self, engine, Engine};

fn decode() -> /* ? */ {
    let input = std::fs::read_to_string("input")?;
    for line in input.lines() {
        let bytes = engine::general_purpose::STANDARD.decode(line)?;
        println!("{}", String::from_utf8(bytes)?);
    }
    Ok(())
}

应对方法是使用trait object: Box。这是可行的,因为所有类型都实现了std::error::Error。

fn decode() -> Result<(), Box<dyn std::error::Error>> {
  // ...
}

虽然这在某些情况下是合适的,但它限制了调用者识别decode()中发生的实际错误的能力。然后,如果希望以不同的方式处理具体错误,则需要使用enum定义错误类型:

enum AppError {
    ReadError(std::io::Error),
    DecodeError(base64::DecodeError),
    StringError(std::string::FromUtf8Error),
}

通过实现std::error::Error trait,我们可以在语义上将AppError标记为错误类型。

impl std::error::Error for AppError {}

然而,这段代码无法编译,因为AppError不满足std::error::Error需要Display和Debug的约束:

error[E0277]: `AppError` doesn't implement `std::fmt::Display`
error[E0277]: `AppError` doesn't implement `Debug`

std::error::Error的定义代表了Rust中对错误类型的最低要求的共识。错误应该对用户(显示)和程序员(调试)有两种形式的描述,并且应该提供其最终错误原因。

pub trait Error: Debug + Display {
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
    // ...
}

在实现所需的特征后,代码将是这样的:

impl std::error::Error for AppError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        use AppError::*;
        match self {
            ReadError(e) => Some(e),
            DecodeError(e) => Some(e),
            StringError(e) => Some(e),
        }
    }
}

impl std::fmt::Display for AppError { // Error message for users.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use AppError::*;
        let message = match self {
            ReadError(_) => "Failed to read the file.",
            DecodeError(_) => "Failed to decode the input.",
            StringError(_) => "Failed to parse the decoded bytes.",
        };
        write!(f, "{message}")
    }
}

impl std::fmt::Debug for AppError { // Error message for programmers.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{self}")?;
        if let Some(e) = self.source() { // <-- Use source() to retrive the root cause.
            writeln!(f, "\tCaused by: {e:?}")?;
        }
        Ok(())
    }
}

到现在可以在decode()中使用AppError:

fn decode() -> Result<(), AppError> {
    let input = std::fs::read_to_string("input").map_err(AppError::ReadError)?;
    // ...

map_err() 用于将std::io::Error转换为AppError::ReadError。使用?操作符为了更好的流程,我们可以为AppError实现From trait:

impl From<std::io::Error> for AppError {
    fn from(value: std::io::Error) -> Self {
        AppError::ReadError(value)
    }
}

impl From<base64::DecodeError> for AppError {
    fn from(value: base64::DecodeError) -> Self {
        AppError::DecodeError(value)
    }
}

impl From<std::string::FromUtf8Error> for AppError {
    fn from(value: std::string::FromUtf8Error) -> Self {
        AppError::StringError(value)
    }
}

fn decode() -> Result<(), AppError> {
    let input = std::fs::read_to_string("input")?;
    for line in input.lines() {
        let bytes = engine::general_purpose::STANDARD.decode(line)?;
        println!("{}", String::from_utf8(bytes)?);
    }
    Ok(())
}

fn main() {
    if let Err(error) = decode() {
        println!("{error:?}");
    }
}

我们做了几件事来流畅地使用自定义错误类型:

  • 实现std::error::error
  • 实现Debug和Display
  • 实现From

上面代码实现有点冗长而乏味,但幸运的是,thiserror会自动生成其中的大部分。

thiserror简化错误定义

下面使用thiserror包简化上面代码:

#[derive(thiserror::Error)]
enum AppError {
    #[error("Failed to read the file.")]
    ReadError(#[from] std::io::Error),
    #[error("Failed to decode the input.")]
    DecodeError(#[from] base64::DecodeError),
    #[error("Failed to parse the decoded bytes.")]
    StringError(#[from] std::string::FromUtf8Error),
}

impl std::fmt::Debug for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{self}")?;
        if let Some(e) = self.source() {
            writeln!(f, "\tCaused by: {e:?}")?;
        }
        Ok(())
    }
}

#[error]宏生成Display, #[from]宏处理from实现source()转换std::error::error。Debug的实现仍然是提供详细的错误消息,但如果够用的话,也可以使用#derive[Debug]:

// The manual implementation of Debug
Failed to decode the input.
        Caused by: InvalidPadding

// #[derive(Debug)]
DecodeError(InvalidPadding)

anyhow处理任何错误

在 Rust 中,anyhow是一个用于方便地处理错误的库。它提供了一种简单的方式来处理各种类型的错误,将不同的错误类型统一转换为anyhow::Error类型,使得错误处理更加灵活和简洁。anyhow构建在std::error::Error的基础上,允许在函数之间轻松地传播错误,而不需要在每个函数签名中指定具体的错误类型。

anyhow提供了简化错误处理的替代方法,类似于Box方法,下面是上面示例的再次实现:

fn decode() -> Result<(), anyhow::Error> {
    let input = std::fs::read_to_string("input")?;
    for line in input.lines() {
        let bytes = engine::general_purpose::STANDARD.decode(line)?;
        println!("{}", String::from_utf8(bytes)?);
    }
    Ok(())
}

它可以编译,因为实现std::error:: error的类型可以转换为anyway::error。错误消息如下:

Invalid padding

为了输出更多错误消息,可以使用contex():

let bytes = engine::general_purpose::STANDARD
    .decode(line)
    .context("Failed to decode the input")?;

现在错误消息为:

Failed to decode the input

Caused by:
    Invalid padding

现在,由于anyway的类型转换和context(),我们的错误处理得到了简化。

异步编程anyhow应用

  • 在异步编程中,anyhow也可以很好地用于处理异步操作可能出现的错误。
  • 例如,一个异步函数用于从网络获取数据并进行处理,可能会遇到网络请求失败、数据解析错误等情况。
  • 以下是示例代码(假设使用tokio进行异步编程):
use anyhow::{anyhow, Result};
use tokio::io::AsyncReadExt;
use tokio::net::TcpStream;

async fn get_and_process_data() -> Result<String> {
    let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
    let mut buffer = [0; 1024];
    let n = stream.read(&mut buffer).await?;
    let data = String::from_utf8_lossy(&buffer[..n]);
    // 假设这里有一个简单的处理逻辑,可能会出错
    if data.is_empty() {
        return Err(anyhow!("Received empty data"));
    }
    Ok(data.into_owned())
}

在这个异步函数中,TcpStream::connectstream.read可能会返回错误,通过?操作符可以方便地将anyhow::Error类型的错误向上传播。

最后总结

总之,我们已经探索了thiserror 和 anyhow库的独特特性,并讨论了每个库的优点。通过选择合适的工具,Rust开发人员可以大大简化错误处理并增强代码的可维护性。

  • thiserror简化实现自定义错误类型,thiserror对于库开发来说是理想的,其中提供的宏对程序员非常友好
  • anyhow库集成任何std::error::Error,anyhow适用于内部细节不重要的应用程序,为用户提供简化的信息

http://www.kler.cn/a/374262.html

相关文章:

  • 【笔记】数据结构与算法
  • NLP segment-02-聊一聊关键词提取 keyword
  • 目录遍历漏洞
  • Unreal Engine 5 C++(C#)开发:使用蓝图库实现插件(一)认识和了解Build.cs
  • 社区交流系统设计与实现
  • 代码随想录day15 二叉树(3)
  • 使用 flex 简单实现 table 自适应页面
  • Puppeteer点击系统:解锁百度流量点击率提升的解决案例
  • 函数声明不是原型error: function declaration isn’t a prototype
  • Spearman相关系数和P值计算的MATLAB代码
  • QT MVC 架构
  • vue系列=模板语法
  • 数据建模圣经|数据模型资源手册卷1,探索数据建模之路
  • 2024 Rust现代实用教程:Ownership与结构体、枚举
  • 《把握鸿蒙生态崛起机遇,迎接开发挑战》
  • 连锁收银系统的优势与挑战
  • 深度解析CAN-FD与CAN协议的差别
  • Python学习之基本语法
  • mysql 单汉字获取大写首拼(自定义函数)
  • Java Executor ScheduledThreadPoolExecutor 源码
  • vue canvas 把两个一样大小的base64 4图片合并成一张上下或者左右图片
  • 15分钟学 Go 第 31 天:单元测试
  • ARB链挖矿DApp系统开发模式定制
  • Jetson Orin NX平台自研载板 IMX477相机掉线问题调试记录
  • 【bug解决】 ImportError: /lib64/libstdc++.so.6: version `CXXABI_1.3.8‘ not found
  • 利用AWS服务轻松迁移数据上云