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

[2023.09.18]: Rust中类型转换在错误处理中的应用解析

随着项目的进展,关于Rust的故事又翻开了新的一页,今天来到了服务器端的开发场景,发现错误处理中的错误类型转换有必要分享一下。
Rust抽象出来了Result<T,E>,T是返回值的类型,E是错误类型。只要函数的返回值的类型被定义为Resut<T,E>,那么作为开发人员就有责任来处理调用这个函数可能发生的错误。通过Result<T,E>,Rust其实给开发人员指明了一条错误处理的道路,使代码更加健壮。

场景

  1. 服务器端处理api请求的框架:Rocket
  2. 服务器端处理数据持久化的框架:tokio_postgres

在api请求的框架中,我把返回类型定义成了Result<T, rocket::response::status::Custom\<String>>,即错误类型是rocket::response::status::Custom\<String>
在tokio_postgres中,直接使用tokio_postgres::error::Error

即如果要处理错误,就必须将tokio_postgres::error::Error转换成rocket::response::status::Custom\<String>。那么我们从下面的原理开始,逐一领略Rust的错误处理方式,通过对比找到最合适的方式吧。

原理

对错误的处理,Rust有3种可选的方式

  1. 使用match
  2. 使用if let
  3. 使用map_err

下面我结合场景,逐一演示各种方式是如何处理错误的。
下面的代码中涉及到2个模块(文件)。/src/routes/notes.rs是路由层,负责将api请求导向合适的service。/src/services/note_books.rs是service层,负责业务逻辑和数据持久化的处理。这里的逻辑也很简单,就是route层调用service层,将数据写入到数据库中。

使用match

src/routes/notes.rs

#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {insert_or_update_note(&note.into_inner()).await
}

/src/services/note_book.rs

pub async fn insert_or_update_note(note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {let (client, connection) = match connect("host=localhost dbname=notes_db user=postgres port=5432",NoTls,).await{Ok(res) => res,Err(err) => {return Err(rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", err),));}};...match client.execute("insert into notes (id, title, content) values($1, $2, $3);",&[&get_system_seconds(), &note.title, &note.content],).await{Ok(res) => Ok(()),Err(err) => Err(rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", err),)),}
}

通过上面的代码我们可以读出一下内容:

  1. 在service层定义了route层相同的错误类型
  2. 在service层将持久层的错误转换成了route层的错误类型
  3. 使用match的代码量还是比较大

使用if let

/src/services/note_book.rs

pub async fn insert_or_update_note(note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {if let Ok((client, connection)) = connect("host=localhost dbname=notes_db user=postgres port=5432",NoTls,).await{...if let Ok(res) = client.execute("insert into notes (id, title, content) values($1, $2, $3);",&[&get_system_seconds(), &note.title, &note.content],).await{Ok(())} else {Err(rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", "unknown error"),))}} else {Err(rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", "unknown error"),))}
}

src/routes/notes.rs

#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {insert_or_update_note(&note.into_inner()).await
}

使用了if let ...,代码更加的别扭,并且在else分支中,拿不到具体的错误信息。

其实,不难看出,我们的目标是将api的请求,经过route层和service层,将数据写入到数据中。但这其中的错误处理代码的干扰就特别大,甚至要有逻辑嵌套现象。这种代码的已经离初衷比较远了,是否有更加简洁的方式,使代码能够最大限度的还原逻辑本身,把错误处理的噪音降到最低呢?
答案肯定是有的。那就是map_err

map_err

map_err是Result上的一个方法,专门用于错误的转换。下面的代码经过了map_err的改写,看上去是不是清爽了不少啊。
/src/services/note_book.rs

pub async fn insert_or_update_note(note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {let (client, connection) = connect("host=localhost dbname=notes_db user=postgres port=5432",NoTls,).await.map_err(|err| {rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", err),)})?;...let _ = client.execute("insert into notes (id, title, content) values($1, $2, $3);",&[&get_system_seconds(), &note.title, &note.content],).await.map_err(|err| {rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!("{}", err),)})?;Ok(())
}

src/routes/notes.rs

#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {insert_or_update_note(&note.into_inner()).await
}

经过map_err改写后的代码,代码的逻辑流程基本上还原了逻辑本身,但是map_err要额外占4行代码,且错误对象的初始化代码存在重复。在实际的工程项目中,service层的处理函数可能是成百上千,如果再乘以4,那多出来的代码量也不少啊,这会给后期的维护带来不小的压力。

那是否还有改进的空间呢?答案是Yes。
Rust为我们提供了From<T> trait,用于类型转换。它定义了从一种类型T到另一种类型Self的转换方法。我觉得这是Rust语言设计亮点之一。
但是,Rust有一个显示,即实现From<T> trait的结构,必须有一个在当前的crate中,也就是说我们不能直接通过From<T>来实现从tokio_postgres::error::Errorrocket::response::status::Custom<String>。也就是说下面的代码编译器会报错。

impl From<tokio_postgres::Error> for rocket::response::status::Custom<String> {}

报错如下:

32 | impl From<tokio_postgres::Error> for rocket::response::status::Custom<String> {}| ^^^^^---------------------------^^^^^----------------------------------------| |    |                               || |    |                               `rocket::response::status::Custom` is not defined in the current crate| |    `tokio_postgres::Error` is not defined in the current crate| impl doesn't use only types from inside the current crate

因此,我们要定义一个类型MyError作为中间类型来转换一下。
/src/models.rs

pub struct MyError {pub message: String,
}
impl From<tokio_postgres::Error> for MyError {fn from(err: Error) -> Self {Self {message: format!("{}", err),}}
}
impl From<MyError> for rocket::response::status::Custom<String> {fn from(val: MyError) -> Self {status::Custom(Status::ExpectationFailed, val.message)}
}

/src/services/note_book.rs

pub async fn insert_or_update_note(note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {let (client, connection) = connect("host=localhost dbname=notes_db user=postgres port=5432",NoTls,).await.map_err(MyError::from)?;...let _ = client.execute("insert into notes (id, title, content) values($1, $2, $3);",&[&get_system_seconds(), &note.title, &note.content],).await.map_err(MyError::from)?;Ok(())
}

src/routes/notes.rs

#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {insert_or_update_note(&note.into_inner()).await
}

MyErrorrocket::response::status::Custom<String>之间的转换是隐式的,由编译器来完成。因此我们的错误类型的转换最终缩短为map_err(|err|MyError::from(err)),再简写为map_err(MyError::from)

关于错误处理中的类型转换应用解析就到这里。通过分析这个过程,我们可以看到,在设计模块时,我们应该确定一种错误类型,就像tokio_postgres库一样,只暴露了tokio_postgress::error::Error一种错误类型。这种设计既方便我们在设计模块时处理错误转换,也方便其我们的模块在被调用时,其它代码进行错误处理。

相关文章:

[2023.09.18]: Rust中类型转换在错误处理中的应用解析

随着项目的进展&#xff0c;关于Rust的故事又翻开了新的一页&#xff0c;今天来到了服务器端的开发场景&#xff0c;发现错误处理中的错误类型转换有必要分享一下。 Rust抽象出来了Result<T,E>&#xff0c;T是返回值的类型&#xff0c;E是错误类型。只要函数的返回值的类…...

前端工作日常

机缘 记录和遇到的问题作为记录 收获 收获代码提高和认知 日常 使用js去操作数组或者对象 空闲时间可以多学学基础算法 比如&#xff08;冒泡&#xff0c;倒序&#xff0c;去重&#xff0c;笛卡尔积算法&#xff0c;各种各样的排序方法等等等&#xff09; 正确良好的使用循环…...

C++:C++哪些时候用到const

声明常量&#xff1a;使用const关键字定义一个常量&#xff0c;不允许对其进行更改。例如&#xff1a; const int PI 3.1415926;修饰函数参数&#xff1a;加上const限定符可以确保函数不会修改传入的参数值。例如&#xff1a; void print(const int num) {// num不能在函数内…...

OpenCV之九宫格图像

将一张图像均等分成九份&#xff0c;然后将这九个小块按一定间隔&#xff08;九宫格效果&#xff09;拷贝到新画布上。效果如下图所示&#xff1a; 源码&#xff1a; #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv;i…...

OpenGLES:绘制一个颜色渐变的圆

一.概述 今天使用OpenGLES实现一个圆心是玫红色&#xff0c;向圆周渐变成蓝色的圆。 本篇博文的内容也是后续绘制3D图形的基础。 实现过程中&#xff0c;需要重点关注的点是&#xff1a;如何使用数学公式求得图形的顶点&#xff0c;以及加载颜色值。 废话不多说&#xff0c…...

javascript数据类型错误造成的前端分页不准的问题

有个react项目是自己写的mock后端api&#xff0c;使用的是json文件模拟DB, slice函数模拟分页&#xff0c;但是在实际分页时&#xff0c;发现了分页不准的问题&#xff0c;现象如下&#xff1a; 当pageSize为5的时候&#xff08;共16条数据&#xff09;&#xff0c;总共分4页&…...

[Qt]QListView 重绘实例之二:列表项覆盖的问题处理

0 环境 Windows 11Qt 5.15.2 MinGW x64 1 系列文章 简介&#xff1a;本系列文章&#xff0c;是以纯代码方式实现 Qt 控件的重构&#xff0c;尽量不使用 Qss 方式。 《[Qt]QListView 重绘实例之一&#xff1a;背景重绘》 《[Qt]QListView 重绘实例之二&#xff1a;列表项覆…...

Java 函数式编程思考 —— 授人以渔

引言 最近在使用函数式编程时&#xff0c;突然有了一点心得体会&#xff0c;简单说&#xff0c;用好了函数式编程&#xff0c;可以极大的实现方法调用的解耦&#xff0c;业务逻辑高度内聚&#xff0c;同时减少不必要的分支语句&#xff08;if-else&#xff09;。 一、函数式编…...

操作系统权限提升(二十八)之数据库提权-SQL Server 数据库安装

SQL Server 数据库安装 SQL Server介绍 SQL Server 是Microsoft 公司推出的关系型数据库管理系统。具有使用方便可伸缩性好与相关软件集成程度高等优点,可跨越从运行Microsoft Windows 98 的膝上型电脑到运行Microsoft Windows 2012 的大型多处理器的服务器等多种平台使用。…...

腾讯mini项目-【指标监控服务重构-会议记录】2023-08-18

2023-08-18 会议纪要 进度 venus 的 metrics 独立分支开发venus 的 trace 修复了一些bug 返回 error 主动调用 span.end() profile 的 watemill pub/sub 和 trace 上报还原原本功能profile 的 hyperscan 的继续调研 待办 调研如何关闭otel&#xff0c;设置开关配置性能benc…...

如何通过axios拦截器,给除了登录请求以外,axios的所有异步请求添加JWT令牌!

在 Vue 项目中配置除了登录请求以外的所有请求的令牌&#xff0c;通常涉及到在请求头中添加令牌&#xff08;Token&#xff09;信息。这可以通过使用 Axios 或其他 HTTP 请求库来实现。以下是一般的步骤&#xff1a; 1. **安装 Axios**&#xff1a; 如果你还没有安装 Axios&a…...

Spring学习笔记9 SpringIOC注解式开发

Spring学习笔记8 Bean的循环依赖问题_biubiubiu0706的博客-CSDN博客 注解的存在主要是为了简化XML的配置.Spring6倡导全注解式开发 回顾下 注解怎么定义,注解中的属性怎么定义 注解怎么使用 通过反射机制怎么读取注解 注解的自定义 注解的使用 通过反射机制怎么读取注解 I…...

【新日标习题集】第13課 までのまとめ (discarded)

2. 学校にコンピューターがごだいあります。 这个句子好像有点问题&#xff0c;辞典中没有查到有「ごだい」这个单词 学校里有5台电脑。 5. わたしは英語がよくわかります。 我很懂英语。...

Java基础常考知识点(基础、集合、异常、JVM)

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有需要我的支持&#xff0c;请私信或评论留言&#xff01; Java基础常考知识点…...

虚拟机桥接模式下没有无线网卡选项

我以为是雷电模拟器占用了网卡的缘故&#xff0c;但想起之前可能修改了无线网卡的某些内容&#xff0c;于是到网络属性里面查看。 如下所示&#xff0c;原来是之前我不小心把这个红箭头指向的项目取消勾选了。...

设计模式笔记

关于设计模式 1. 如何阅读本文 略 2. 面向对象程序设计简介 2.1 面向对象程序设计基础 面向对象程序设计 &#xff08;Object-Oriented Programming&#xff0c;缩写为 OOP&#xff09;是一种范式&#xff0c;其基本理念是将 数据块 及 与数据相关的行为 封装成为特殊的、…...

c==ubuntu+vscode debug redis7源码

新建.vscode文件夹&#xff0c;创建launch.json和tasks.json {"version": "0.2.0","configurations": [{"name": "C/C Launch","type": "cppdbg","request": "launch","prog…...

java字符串储存底层原理

字符串原理:原理1: 内存原理 (1)直接赋值给字符串,会把这个字符串放到常量池里,如果之后出现重复使用这个字符串的,就会直接从这个常量池中去引用,不会再去new一个字符串 (2)new出来的字符串不会重复使用,而是开辟一个新的空间存储原理2: 字符串中的""比较的是什么?…...

c++获取当前时间的字符串

代码 void getNowTimePrefix(std::string& prefix) {std::time_t nowTime;struct tm* p new tm;std::time(&nowTime);localtime_s(p, &nowTime);int year p->tm_year 1900;int month p->tm_mon 1;int day p->tm_mday;int hour p->tm_hour;int …...

【精品】通用Mapper 批量更新bug解决方案

问题描述 环境&#xff1a;mysql8.xmybatis3.5.13tk.mybatis4.2.3 在使用tk.mybatis做批量更新时&#xff0c;程序会报错&#xff0c;说是执行的SQL语法错误&#xff0c;经研究源代码发现tk.mybatis在实现批量更新时是通过多次执行update语句实现的。这本身就不符合MySQL批量…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...