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

Rust的错误处理

概述

Rust 偷师 Haskell,构建了对标Maybe的Option 类型和对标Either 的Result 类型

请添加图片描述

Option 和 Result

Option 是一个 enum,其定义如下

pub enum Option<T> {None,Some(T)
}

它可以承载有值 / 无值这种最简单的错误类型

Result 是一个更加复杂的 enum, 其定义如下:

#[must_use = "this `Result` may be an `Err` variant, which should be handle"]
pub enum Result<T, E> {Ok(T),Err(E)
}

当函数出错时,可以返回Err(E), 否则 Ok(T)

Result 类型声明时还有个must_use 的标注,编译器会对有 must_use 标注的所有类型做特殊处理

  • 如果该类型对应的值没有被显式使用,则会告警。这样,保证错误被妥善处理

?操作符

所以在Rust 代码中,如果你只想传播错误,不想就地处理,可以用 ?操作符

use std::fs::File;
use std::io::Read;fn read_file(name: &str) -> Result<String, std::io::Error> {let mut f = File::open(name)?;let mut contents = String::new();f.read_to_string(&mut contents);Ok(contents);
}

通过 ?操作符,Rust 让错误传播的代价和异常处理不相上下,同时又避免了异常处理的诸多问题

? 操作符内部被展开成类似这样的代码

match result {Ok(v) => v,Err(e) => return Err(e.into())
}

所有,我们可以方便写出类似这样的代码,简洁易懂,可读性很强

fut.await?.process()?.next().await?;

整个代码的执行流程如下:
请添加图片描述

虽然 ? 操作符使用起来非常方便,但要注意在不同的错误类型之间是无法直接使用的,需要实现From trait 在二者之间建立起转换的桥梁,这会带来额外的麻烦

函数式错误处理

Rust 还为Option 和 Result 提供了大量的辅助函数,如 map / map_err / add_then, 你可以很方便地处理数据结构中部分情况

请添加图片描述

请添加图片描述

通过这些函数,可以很方便地对错误处理引入 Railroad oriented programming 范式。比如用户注册的流程,你需要校验用户输入,对数据进行处理,转换,然后存入数据中

Ok(data).add_then(validate).add_then(process).map(transform).and_then(store).map_error(...)

执行流程如下图所示:
请添加图片描述

此外,Option 和 Result 的互相转换也很方便,这也得益于Rust 构建的强大的函数式编程能力

panic! 和 catch_unwind

使用 Option 和 Result 是 Rust 中处理错误的首选,绝大多数时候我们也应该使用,但Rust 也提供了特殊的异常处理能力

在 Rust 看来,一旦你需要抛出异常,那抛出的一定是严重的错误。所以,Rust 跟 Golang 一样,使用了诸如panic! 这样的字眼警示开发者

  • 想清楚了再使用我

在使用Option 和 Result 类型时,开发者也可以对其unwarp() 或者 expect(), 强制把Option 和 Reulst<T, E> 转换成 T,如果无法完成这种转换,也会panic! 出来

一般而言,panic! 是 不可恢复或者不想恢复错误。希望在此刻,程序终止运行并得到崩溃信息

比如下面的代码,它解析 noise protoco 的协议变量

let params : NoiseParams = "Noise_XX_25519_AESGCM_SHA256".parse().unwrap();

如果开发者小小心把协议变量写错了,最佳的方式是立刻panic! 出来,让错误立刻暴露,以便解决这个问题

有些场景下,也希望能够像异常处理那样能够栈回溯,把环境恢复到捕获异常的上下文。Rust 标准库下提供了catch_unwind(), 把调用栈回溯到 catch_unwind 这一刻,作用和其他语言的 try {…} catch {…}

use std::painic;fn main() {let result = panic::catch_unwind(|| {println("hello!");});assert!(result.is_ok());let result = panic::catch_unwind(|| {panic!("oh no!");});assert!(result.is_err());println!("panic captured: {:#?}", result);
}

当然,和异常处理一样,并不意味你可以溢出这一特性,我想,这也是Rust 把 抛出异常称作 panic!, 而捕获异常称作 catch_unwind 的原因,让初学者望而生畏,不敢轻易使用

catch_unwind 在某些场景下非常有用,比如你在使用 Rust 为 erlang VM 撰写 NIF,你不希望Rust 代码中的任何 panic! 导致 erlang VM 崩溃。

因为崩溃是一个非常不好的体验,它违背了 erlang 的 设计原则: process 可以 let it crash ,但错误代码不该导致 VM 崩溃

你就可以把Rust 代码整个封装在 catch_unwind() 函数所需要传入的闭包中,这样,一旦任何代码中,包括第三方crates 的代码,含有能够导致 panic! 的代码,都会被捕获,并被转换为一个Result

Error trait 错误类型的转换

为了规范这个代表错误的数据类型行为,Rust 定义了 Error trait

pub trait Error: Debug + Display {fn source(&self) -> Option<&(dyn Error + 'static)> {...}fn backtrace(&self) -> Option<&Backtrace> {...}fn description(&self) -> &str {...}fn cause(&self) -> Option<&dyn Error> {...}
}

定义自己的数据类型,然后为其实现 Error trait

不过,这样的工作已经有人替人简化了,可以使用 thiserror 和 anyhow 来简化这个步骤。thiserror 提供了一个派生宏(drive macro) 来简化错误类型的定义

use thiserror::Error;
#[dervie(Error, Debug)]
#[non_exhaustive]
pub enum DataStoreError {#[error("data store disconnected")]Disconnect(#[from] io::Error),#[error("the data for key `{0}` is not available")]Redaction(String)#[error("invalid header (expected {expected:?}, found {found:?})")]InvalidHeader {expected: String,found: String},#[error("unknown data store error")]Unknown,
}

如果你在撰写一个Rust 库,那么thiserror 可以很好地协助你对这个库里所有可能发生的错误进行建模

而anyhow 实现了 anyhow::Error 和 任意符号 Error trait 的错误类型之间的转换,让你可以使用?操作符,不必再手工转换错误类型

anyhow 还可以让你容易抛出一些临时的错误,而不必费力定义错误类型,当然,不提倡滥用这个能力

建议开发前,先用类似 thiserror 的库定义好你项目中主要的错误类型,并随着项目的深入,不断增加新的错误类型,让系统中所有的潜在错误无所遁形

http://www.dtcms.com/a/434282.html

相关文章:

  • 可视化地图
  • Rust与C接口交互
  • 【C++实战(64)】C++ 邂逅SQLite3:数据库编程实战之旅
  • 泉州网页建站模板开发网址
  • 中华建设杂志网站记者管理网站英文
  • React 18+TS中使用Cesium 1.95
  • View:new关键词干了什么事,还有原型链是什么
  • 如何在新的Spring Boot项目中关闭Spring Security?
  • 药企做网站需要哪些手续国内新闻最新消息今天在线
  • 【Flutter】GetX最佳实践与避坑指南
  • AIFoto 1.15.4 | AI图片工具,AI擦除衣服,变性感衣服
  • 数据合规与ISO标准体系
  • 在Ubuntu22.04系统下安装Jellyfin
  • 福州做网站的app排名优化公司
  • 【Linux系统】快速入门一些常用的基础指令
  • AI自动化测试:接口测试全流程自动化的实现方法——从需求到落地的全链路实践
  • 打开网站建设中是什么意思表白网站制作代码
  • 【MySQL】MVCC:从核心原理到幻读解决方案
  • Unity游戏基础-4(人物移动、相机移动、UI事件处理 代码详解)
  • 神经网络中优化器的作用
  • 电子商务网站建设的流程图什么是软文
  • 【代码管理】git使用指南(新手向)
  • 【大模型】Agent之:从Prompt到Context的演进之路
  • Docker 搭建 Nginx 并启用 HTTPS 具体部署流程
  • 【代码随想录day 34】 力扣 62.不同路径
  • 点击app图标进入网站怎么做小程序软件开发制作
  • 【Rust GUI开发入门】编写一个本地音乐播放器(15. 记录运行日志)
  • Rust模式匹配详解
  • 石家庄做网站建设公司安徽省建设厅网站职称申报
  • gitlab-runner 再次实践中理解和学习