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 inputCaused 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.rhkb.cn/news/465092.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vim文本编辑器gcc编译器gdb调试器】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、vimvim安装vim常用快捷键vim使用vimtutor zh文档 二、gcc编译器安装gcc工具编译源代码 三、gdb调试器gdb安装gdb常用指令gdb简单上手使用gdb的单步调试功能 总结…

企业数字化转型的架构治理策略:核心问题、深度分析与优化路径

在当今的商业环境中&#xff0c;企业数字化转型已成为实现可持续发展、增强竞争力的战略选择。企业架构治理&#xff08;Enterprise Architecture Governance Capability, EAGC&#xff09;在数字化转型中扮演着保障架构一致性、提升变革效能的关键角色。本指南深入解析了如何通…

基于springboot+vue实现的农产品物流系统

基于springbootvue实现的农产品物流系统 &#xff08;源码L文ppt&#xff09;4-107 摘 要 随着现代信息技术的迅猛发展&#xff0c;农产品物流系统应运而生&#xff0c;成为连接生产者与消费者的重要桥梁。该系统采用java语言&#xff0c; Spring Boot框架&#xff0c;结合My…

基于uniapp和java的电动车智能充电系统软件平台的设计

文章目录 项目介绍具体实现截图技术介绍mvc设计模式小程序框架以及目录结构介绍错误处理和异常处理java类核心代码部分展示详细视频演示源码获取 项目介绍 对电动车智能充电系统进行设计和开发。通过使用本系统可有效地减少运营成本&#xff0c;提高管理效率。 根据近年来社会…

Jmeter命令监控CPU等指标

JMeter 命令行执行脚本得到的报告中&#xff0c;是没有CPU、内存使用率等监控数据的&#xff0c;但是可以使用JMeter插件帮忙。 一、下载jmeter-plugins-manager.jar 下载后将文件放到jmeter安装包lib/ext目录下。打开Jmeter》菜单栏》选项》Plugins Manager 二、安装PerfMon…

ubuntu20.04 加固方案-检查是否设置登录超时

一、编辑/etc/profile配置文件 打开终端。 使用文本编辑器&#xff08;如vim&#xff09;编辑/etc/profile 文件。 vi /etc/profile 二、添加配置参数 在打开的配置文件中&#xff0c;如图位置添加如下参数&#xff1a; TMOUT1800 export TMOUT三、保存并退出 在vim编辑器…

HarmonyOS使用arkTS拉起指定第三方应用程序

HarmonyOS使用arkTS拉起指定第三方应用程序 前言代码及说明bundleName获取abilityName获取 前言 本篇只说采用startAbility方式拉起第三方应用&#xff0c;需要用到两个必备的参数bundleName&#xff0c;abilityName&#xff0c;本篇就介绍如何获取参数… 代码及说明 bundle…

32位汇编——通用寄存器

通用寄存器 什么是寄存器呢&#xff1f; 计算机在三个地方可以存储数据&#xff0c;第一个是把数据存到CPU中&#xff0c;第二个把数据存到内存中&#xff0c;第三个把数据存到硬盘上。 那这个所谓的寄存器&#xff0c;就是CPU中用来存储数据的地方。那这个寄存器有多大呢&a…

江协科技STM32学习- P35 硬件I2C读写MPU6050

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

【大数据学习 | HBASE】habse的表结构

在使用的时候hbase就是一个普通的表&#xff0c;但是hbase是一个列式存储的表结构&#xff0c;与我们常用的mysql等关系型数据库的存储方式不同&#xff0c;mysql中的所有列的数据是按照行级别进行存储的&#xff0c;查询数据要整个一行查询出来&#xff0c;不想要的字段也需要…

【dvwa靶场:XSS系列】XSS (Reflected)低-中-高级别,通关啦

一、低级low 简单拿捏 <script>alert(123)</script>二、中级middle 源码过滤了script但是没有过滤大小写&#xff0c;改成大写S <Script>alert(123)</script>三、高级high 比中级高&#xff0c;过滤了script并且以及大小写&#xff0c;使用其他标…

NAT实验

一、网络拓扑 二、实验步骤 1.配ip地址 用缺省路由充当网关 2.配置telent服务 3.配置公网互通&#xff0c;在PC1上ping R3的公网地址&#xff0c;测试是否可以访问互联网 [R1]ip route-static 0.0.0.0 0 10.1.1.2 [R3]ip route-static 0.0.0.0 0 10.2.2.2 此时私网是ping不通…

Centos 7系统一键安装宝塔教程

服务器推荐青鸟云服务器&#xff0c;2H2G低至16元/月 官网地址&#xff1a; 所有产品_香港轻量云 2核 2G-A型_青鸟云 推荐Finalshell软件连接至服务器&#xff0c;下载地址&#xff1a; https://dl.hostbuf.com/finalshell3/finalshell_windows_x64.exe 下载完成后连接服务…

Kafka 之顺序消息

前言&#xff1a; 在分布式消息系统中&#xff0c;消息的顺序性是一个重要的问题&#xff0c;也是一个常见的业务场景&#xff0c;那 Kafka 作为一个高性能的分布式消息中间件&#xff0c;又是如何实现顺序消息的呢&#xff1f;本篇我们将对 Kafka 的顺序消息展开讨论。 Kafk…

SpringBoot day 1105

ok了家人们&#xff0c;今天继续学习spring boot&#xff0c;let‘s go 六.SpringBoot实现SSM整合 6.1 创建工程&#xff0c;导入静态资源 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</…

fastbootd模式刷android固件的方法

1. fastbootd追根溯源 Google在Android 10上正式引入了动态分区机制来提升OTA的可扩展性。动态分区使能后&#xff1a;andorid系统可以在开机阶段动态地进行分区创建、分区销毁、分区大小调整等操作&#xff0c;下游厂商只需要规划好super分区的总大小&#xff0c;其内部的各个…

什么是多因素身份验证(MFA)的安全性?

多因素身份验证(MFA)简介 什么是MFA 多因素身份验证(MFA)是一种安全过程&#xff0c;要求用户在授予对系统、应用程序或账户的访问权限之前提供两种或多种形式的验证。仅使用单个因素&#xff08;通常是用户名和密码&#xff09;保护资源会使它们容易受到泄露&#xff0c;添加…

2024年【汽车修理工(高级)】考试总结及汽车修理工(高级)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车修理工&#xff08;高级&#xff09;考试总结是安全生产模拟考试一点通总题库中生成的一套汽车修理工&#xff08;高级&#xff09;试题及解析&#xff0c;安全生产模拟考试一点通上汽车修理工&#xff08;高级&a…

qt QFontDialog详解

1、概述 QFontDialog 是 Qt 框架中的一个对话框类&#xff0c;用于选择字体。它提供了一个可视化的界面&#xff0c;允许用户选择所需的字体以及相关的属性&#xff0c;如字体样式、大小、粗细等。用户可以通过对话框中的选项进行选择&#xff0c;并实时预览所选字体的效果。Q…

DolphinScheduler告警通知

DolphinScheduler告警通知 Dolphinscheduler支持多种告警媒介&#xff0c;此处以电子邮件为例进行演示。 1 准备邮箱 如需使用DolphinScheduler的电子邮件告警通知功能&#xff0c;需要准备一个电子邮箱账号&#xff0c;并启用SMTP服务。此处以 QQ 邮箱为例。 1.1 开启 SMTP 服…