Rust 错误处理库: thiserror 和 anyerror

news2024/11/6 4:20:49

在这篇博文中,我们将探索在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.coloradmin.cn/o/2234048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构——二叉树(续集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ 在上一篇博客我们…

动态规划 —— dp问题-按摩师

1. 按摩师 题目链接&#xff1a; 面试题 17.16. 按摩师 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/the-masseuse-lcci/description/ 2. 算法原理 状态表示&#xff1a;以某一个位置为结尾或者以某一个位置为起点 dp[i]表示&#xff1a;选择到i位置…

python爬取旅游攻略(1)

参考网址&#xff1a; https://blog.csdn.net/m0_61981943/article/details/131262987 导入相关库&#xff0c;用get请求方式请求网页方式&#xff1a; import requests import parsel import csv import time import random url fhttps://travel.qunar.com/travelbook/list.…

C++设计模式创建型模式———原型模式

文章目录 一、引言二、原型模式三、总结 一、引言 与工厂模式相同&#xff0c;原型模式&#xff08;Prototype&#xff09;也是创建型模式。原型模式通过一个对象&#xff08;原型对象&#xff09;克隆出多个一模一样的对象。实际上&#xff0c;该模式与其说是一种设计模式&am…

基于STM32的智能温室环境监测与控制系统设计(代码示例)

一、项目概述 在现代农业中&#xff0c;智能大棚能够通过环境监测、数据分析和自动控制等技术手段&#xff0c;实现对作物生长环境的精细化管理。本项目旨在设计一个基于STM32单片机的智能大棚系统&#xff0c;能够实时监测光照强度、空气温湿度及土壤湿度&#xff0c;并根据设…

(五)Web前端开发进阶2——AJAX

目录 1.Ajax概述 2.Axios库 3.认识URL 4.Axios常用请求方法 5.HTTP协议——请求报文/响应报文 6.HMLHttpRequest对象 7.前后端分离开发&#xff08;接口文档&#xff09; 8.Element组件库 1.Ajax概述 AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。…

进程信号——信号的保存

信号的概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 注意,阻塞和忽略是不同的,只要信号…

基于SSM的“房屋租赁系统”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“房屋租赁系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM&#xff0c;JSP 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 房屋租赁系统首页 管理员后台管理页面 报告故障管…

无需懂代码!用AI工具Bolt一键生成网站的入门指南!

​ ​ 随着AI技术的不断发展&#xff0c;许多原本需要技术门槛的操作正在被大大简化&#xff0c;甚至零基础的用户也可以轻松实现。 例如&#xff0c;AI生成网站工具Bolt就是这样一个可以帮助我们快速创建、实时预览并自动部署网站的平台。接下来&#xff0c;本文将带你深入了…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

vue中el-table显示文本过长提示

1.el-table设置轻提示:show-overflow-tooltip“true“&#xff0c;改变轻提示宽度

关于我的编程语言——C/C++——第四篇(深入1)

&#xff08;叠甲&#xff1a;如有侵权请联系&#xff0c;内容都是自己学习的总结&#xff0c;一定不全面&#xff0c;仅当互相交流&#xff08;轻点骂&#xff09;我也只是站在巨人肩膀上的一个小卡拉米&#xff0c;已老实&#xff0c;求放过&#xff09; 字符类型介绍 char…

【春秋云镜】CVE-2023-23752

目录 CVE-2023-23752漏洞细节漏洞利用示例修复建议 春秋云镜&#xff1a;解法一&#xff1a;解法二&#xff1a; CVE-2023-23752 是一个影响 Joomla CMS 的未授权路径遍历漏洞。该漏洞出现在 Joomla 4.0.0 至 4.2.7 版本中&#xff0c;允许未经认证的远程攻击者通过特定 API 端…

AI 写作(一):开启创作新纪元(1/10)

一、AI 写作&#xff1a;重塑创作格局 在当今数字化高速发展的时代&#xff0c;AI 写作正以惊人的速度重塑着创作格局。AI 写作在现代社会中占据着举足轻重的地位&#xff0c;发挥着不可替代的作用。 随着信息的爆炸式增长&#xff0c;人们对于内容的需求日益旺盛。AI 写作能够…

快速构建数据产品原型 —— 我用 VChart Figma 插件

快速构建数据产品原型 —— 我用 VChart Figma 插件 10 种图表类型、24 种内置模板类型、丰富的图表样式配置、自动生成图表实现代码。VChart Figma 插件的目标是提供 便捷好用 & 功能丰富 & 开发友好 的 figma 图表创建能力。目前 VChart 插件功能仍在持续更新中&…

源鲁杯 2024 web(部分)

[Round 1] Disal F12查看: f1ag_is_here.php 又F12可以发现图片提到了robots 访问robots.txt 得到flag.php<?php show_source(__FILE__); include("flag_is_so_beautiful.php"); $a$_POST[a]; $keypreg_match(/[a-zA-Z]{6}/,$a); $b$_REQUEST[b];if($a>99999…

【ArcGIS】绘制各省碳排放分布的中国地图

首先&#xff0c;准备好各省、自治区、直辖市及特别行政区&#xff08;包括九段线&#xff09;的shp文件&#xff1a; 通过百度网盘分享的文件&#xff1a;GS&#xff08;2022&#xff09;1873 链接&#xff1a;https://pan.baidu.com/s/1wq8-XM99LXG_P8q-jNgPJA 提取码&#…

C++《list的模拟实现》

在上一篇C《list》专题当中我们了解了STL当中list类当中的各个成员函数该如何使用&#xff0c;接下来在本篇当中我们将试着模拟实现list&#xff0c;在本篇当中我们将通过模拟实现list过程中深入理解list迭代器和之前学习的vector和string迭代器的不同&#xff0c;接下来就开始…

讲讲⾼可用的原则?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲⾼可用的原则&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲讲⾼可用的原则&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在当今信息化时代&#xff0c;随着互联网技术的快速发展&#xff0…

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…