rust中的错误处理 result枚举 rust 中没有提供类似于 java、c++ 中的 exception 机制,而是使用 result 枚举的方式来实现:
pub enum result { /// contains the success value ok(t), /// contains the error value err(e), }
在使用时:
如果无错误则使用 ok(t) 返回;
如果存在错误,则使用 err(e) 包装错误类型返回;
例如:
examples/0_result.rs
#[derive(debug)] pub enum myerror { internal(string), invalidid(string), } fn add(num: i64) -> result { if num result { // fetch_id(-1)?; let res = add(1)?; println!({}, res); ok(()) }
上面的代码首先通过 myerror 枚举定义了多个可能会出现的错误;
随后,在 add 函数中:
当 num 小于 0 时返回错误;
否则给 num 增加 100000 并返回;
在上面的 let res = add(1)?; 中使用了 ? 操作符,他相当于是一个语法糖:
如果被调函数正常返回则调用 unwrap 获取其值;
反之,则将被调函数的错误直接向上返回(相当于直接 return err);
即上面的语法糖相当于:
let res = match add() { ok(id) => id, err(err) => { return err(err); } };
错误类型转换 上面简单展示了 rust 中错误的使用;
由于 rust 是强类型的语言,因此如果在一个函数中使用 ? 返回了多个错误,并且他们的类型是不同的,还需要对返回的错误类型进行转换,转为相同的类型!
例如下面的例子:
#[derive(debug)] pub enum myerror { readerror(string), parseerror(string), } fn read_file() -> result { // error: could not get compiled! let content = fs::read_to_string(/tmp/id)?; let id = content.parse::()?; } fn main() -> result { let id = read_file()?; println!(id: {}, id); ok(()) }
上面的例子无法编译通过,原因在于: read_to_string 和 parse 返回的是不同类型的错误!
因此,如果要能返回,我们需要对每一个错误进行转换,转为我们所定义的 error 类型;
例如:
examples/1_error_convert.rs
fn read_file() -> result { // error: could not get compiled! // let content = fs::read_to_string(/tmp/id)?; // let id = content.parse::()?; // method 1: handling error explicitly! let content = match std::read_to_string(/tmp/id) { ok(content) => content, err(err) => { return err(myerror::readerror(format!(read /tmp/id failed: {}, err))); } }; let content = content.trim(); println!(read content: {}, content); // method 2: use map_err to transform error type let id = content .parse::() .map_err(|err| myerror::parseerror(format!(parse error: {}, err)))?; ok(id) }
上面展示了两种不同的转换 error 的方法:
方法一通过 match 匹配手动的对 read_to_string 函数的返回值进行处理,如果发生了 error,则将错误转为我们指定类型的错误;
方法二通过 map_err 的方式,如果返回的是错误,则将其转为我们指定的类型,这时就可以使用 ? 返回了;
相比之下,使用 map_err 的方式,代码会清爽很多!
from trait 上面处理错误的方法,每次都要对错误的类型进行转换,比较麻烦;
rust 中提供了 from trait,在进行类型匹配时,如果提供了从一个类型转换为另一个类型的方法(实现了某个类型的 from trait),则在编译阶段,编译器会调用响应的函数,直接将其转为相应的类型!
例如:
examples/2_from_trait.rs
#[derive(debug)] pub enum myerror { readerror(string), parseerror(string), } impl from for myerror { fn from(source: std::error) -> self { myerror::readerror(source.to_string()) } } impl from for myerror { fn from(source: std::parseinterror) -> self { myerror::parseerror(source.to_string()) } } fn read_file() -> result { let _content = fs::read_to_string(/tmp/id)?; let content = _content.trim(); let id = content.parse::()?; ok(id) } fn main() -> result { let id = read_file()?; println!(id: {}, id); ok(()) }
在上面的代码中,我们为 myerror 类型的错误分别实现了转换为 std::error 和 std::parseinterror 类型的 from trait;
因此,在 read_file 函数中就可以直接使用 ? 向上返回错误了!
但是上面的方法需要为每个错误实现 from trait 还是有些麻烦,因此出现了 thiserror 以及 anyhow 库来解决这些问题;
其他第三方库 thiserror 上面提到了我们可以为每个错误实现 from trait 来直接转换错误类型,thiserror 库就是使用这个逻辑;
我们可以使用 thiserror 库提供的宏来帮助我们生成到对应类型的 trait;
例如:
examples/3_thiserror.rs
#[derive(thiserror::error, debug)] pub enum myerror { #[error(io error.)] ioerror(#[from] std::error), #[error(parse error.)] parseerror(#[from] std::parseinterror), } fn read_file() -> result { // could get compiled! let content = fs::read_to_string(/tmp/id)?; let id = content.parse::()?; ok(id) } fn main() -> result { let id = read_file()?; println!(id: {}, id); ok(()) }
我们只需要对我们定义的类型进行宏标注,在编译时这些宏会自动展开并实现对应的 trait;
展开后的代码如下:
#![feature(prelude_import)] #[prelude_import] use std::*; #[macro_use] extern crate std; use std::fs; pub enum myerror { #[error(io error.)] ioerror(#[from] std::error), #[error(parse error.)] parseerror(#[from] std::parseinterror), } #[allow(unused_qualifications)] impl std::error for myerror { fn source(&self) -> std::option { use thiserror::asdynerror; #[allow(deprecated)] match self { myerror::ioerror { 0: source, .. } => std::option::some(source.as_dyn_error()), myerror::parseerror { 0: source, .. } => { std::option::some(source.as_dyn_error()) } } } } #[allow(unused_qualifications)] impl std::display for myerror { fn fmt(&self, __formatter: &mut std::formatter) -> std::result { #[allow(unused_variables, deprecated, clippy::used_underscore_binding)] match self { myerror::ioerror(_0) => { let result = __formatter.write_fmt(::new_v1(&[io error.], &[])); result } myerror::parseerror(_0) => { let result = __formatter.write_fmt(::new_v1(&[parse error.], &[])); result } } } } #[allow(unused_qualifications)] impl std::from for myerror { #[allow(deprecated)] fn from(source: std::error) -> self { myerror::ioerror { 0: source } } } #[allow(unused_qualifications)] impl std::from for myerror { #[allow(deprecated)] fn from(source: std::parseinterror) -> self { myerror::parseerror { 0: source } } } #[automatically_derived] #[allow(unused_qualifications)] impl ::debug for myerror { fn fmt(&self, f: &mut ::formatter) -> ::result { match (&*self,) { (&myerror::ioerror(ref __self_0),) => { ::debug_tuple_field1_finish(f, ioerror, &&*__self_0) } (&myerror::parseerror(ref __self_0),) => { ::debug_tuple_field1_finish(f, parseerror, &&*__self_0) } } } } fn read_file() -> result { let content = fs::read_to_string(/tmp/id)?; let id = content.parse::()?; ok(id) } #[allow(dead_code)] fn main() -> result { let id = read_file()?; { ::new_v1( &[id: , ], &[::new_display(&id)], )); }; ok(()) } #[rustc_main] pub fn main() -> () { extern crate test; test::test_main_static(&[]) }
可以看到实际上就是为 myerror 实现了对应错误类型的 from trait;
thiserror 库的这种实现方式,还需要为类型指定要转换的错误类型;
而下面看到的 anyhow 库,可以将错误类型统一为同一种形式;
anyhow 如果你对 go 中的错误类型不陌生,那么你就可以直接上手 anyhow 了!
来看下面的例子:
examples/4_anyhow.rs
use anyhow::result; use std::fs; fn read_file() -> result { // could get compiled! let content = fs::read_to_string(/tmp/id)?; let id = content.parse::()?; ok(id) } fn main() -> result { let id = read_file()?; println!(id: {}, id); ok(()) }
注意到,上面的 result 类型为 anyhow::result,而非标准库中的 result 类型!
anyhow 为 result 实现了 context trait:
impl context for result where e: ext::stderror + send + sync + 'static, { fn context(self, context: c) -> result where c: display + send + sync + 'static, { // not using map_err to save 2 useless frames off the captured backtrace // in ext_context. match self { ok(ok) => ok(ok), err(error) => err(error.ext_context(context)), } } fn with_context(self, context: f) -> result where c: display + send + sync + 'static, f: fnonce() -> c, { match self { ok(ok) => ok(ok), err(error) => err(error.ext_context(context())), } } }
在 context 中提供了 context 函数,并且将原来的 result 转成了 result;
因此,最终将错误类型统一为了 anyhow::error 类型;
附录 源代码:
https://github.com/jasonkayzk/rust-learn/tree/error
中国移动启动带状光缆集采 究竟会花落谁家呢?
一加可能会在下月17号发布新机,主角已经标明了是One Plus 6T
从TWS耳机到咖啡机,解读主动降噪(ANC)技术应用“破圈”背后的“密码”
冰箱压缩机坏了的表现_冰箱压缩机响几秒就停
走进2016“多彩”的LED世界:百家争鸣,精彩纷呈
Rust中的错误处理方法
英众科技推出OPS模块 构建多维互动教育生态
华为LiteOS轻量级物联网操作系统首次引入安防
路由器和交换机应用需要做哪些配置?
惠州雷曼推进新一代信息技术与制造业的深度融合
LCD永不为奴,白菜价iQOO Neo 5活力版真有极高的性价比吗?
基于Java平台的视频监控系统实现对云台和镜头的远程控制
最新信号发生器科技: 2 GHz 带宽的双通道 44 GHz矢量信号发生器
铅蓄电池与锂离子电池的区别和应用领域
采用复杂可编程逻辑器件实现多路信号采集系统的设计
电网谐波的危害和消除措施
HMC141 RF混频器
弹片微针模组在手机屏幕测试中的应用优势
安防智能化还需落地 才能为行业客户提供优质的行业智能解决方案
高性能Nginx HTTPS调优-如何为HTTPS提速30%