Rust的错误处理机制
文章目录
- Rust的错误处理机制
- Rust还会发生崩溃吗?
- 错误的分类
- 不可恢复的错误
- 可恢复的错误
- 传播错误
Rust的错误处理机制
任何语言都需要有错误处理机制,看到主流的语言清一色的try…catch…,Go和Rust又坐不住了,纷纷提出了自己的错误处理机制。Go的错误处理机制被骂的体无完肤,而Rust的错误处理却获赞不少。那么Rust的错误处理有什么独到之处呢,一起来学习一下吧。
Rust还会发生崩溃吗?
即使try…catch…,也有很多不能处理的错误而引发崩溃,是的,任何一种错误处理机制都不可能100%的捕获所有的错误。Rust的内存机制再出色、编译器再强大,也不能保证没有意外的发生。
以最基础的数组越界为例:
fn main() {let a = [1, 2, 3, 4, 5];println!("{}", a[5]);
}
编译器表现得很好,直接器报错:
error: this operation will panic at runtime--> src\main.rs:3:20|
3 | println!("{}", a[5]);| ^^^^ index out of bounds: the len is 5 but the index is 5|= note: `#[deny(unconditional_panic)]` on by defaulterror: aborting due to previous error
很好,那我再换一个写法:
fn main() {let a = [1, 2, 3, 4, 5];let b = 4;println!("{}", a[b+1]);
}
不负众望的编译器还是亮起来红灯:
error: this operation will panic at runtime--> src\main.rs:4:20|
4 | println!("{}", a[b+1]);| ^^^^^^ index out of bounds: the len is 5 but the index is 5|= note: `#[deny(unconditional_panic)]` on by defaulterror: aborting due to previous error
看来我小瞧编译器了。那么,再提升一个难度呢?
use rand::Rng;fn main() {let a = [1, 2, 3, 4, 5];let b = rand::thread_rng().gen_range(5, 999); // 生成一个随机数,这回编译器不知道b的值是多少了println!("{}", a[b]);
}
果然,编译器不是万能的,这次编译通过了,但运行崩溃了:
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 30', src\main.rs:7:20
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
可见,强大如rust也没有办法摆脱崩溃的烦恼。
错误的分类
Rust将运行时错误分为两大类:
- 不可恢复的错误
- 可恢复的错误
对于大部分错误,并没有严重到无方可解的地步,这个时候,Rest采用Result枚举来解决(还记得吗?内置的枚举类型里有过一面之缘);然而,对于某些错误,一旦发生就是致命的,此时,Rust提供了一个叫做panic! 的宏,它会在不可恢复的错误发生之后,清理内存数据,然后停止程序的运行。
不可恢复的错误
使用随机数访问数组这个例子就发生了panic,Rust自动调用了panic! 宏,输出中提到了设置RUST_BACKTRACE=1
这个环境变量来查看调用堆栈的信息:
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 451', src\main.rs:7:20
stack backtrace:0: backtrace::backtrace::dbghelp::traceat C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.46\src\backtrace/dbghelp.rs:881: backtrace::backtrace::trace_unsynchronizedat C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.46\src\backtrace/mod.rs:662: std::sys_common::backtrace::_print_fmtat src\libstd\sys_common/backtrace.rs:783: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmtat src\libstd\sys_common/backtrace.rs:594: core::fmt::writeat src\libcore\fmt/mod.rs:10765: std::io::Write::write_fmtat src\libstd\io/mod.rs:15376: std::sys_common::backtrace::_printat src\libstd\sys_common/backtrace.rs:627: std::sys_common::backtrace::printat src\libstd\sys_common/backtrace.rs:498: std::panicking::default_hook::{{closure}}at src\libstd/panicking.rs:1989: std::panicking::default_hookat src\libstd/panicking.rs:21810: std::panicking::rust_panic_with_hookat src\libstd/panicking.rs:48611: rust_begin_unwindat src\libstd/panicking.rs:38812: core::panicking::panic_fmtat src\libcore/panicking.rs:10113: core::panicking::panic_bounds_checkat src\libcore/panicking.rs:7314: guessing::mainat src/main.rs:715: std::rt::lang_start::{{closure}}at C:\Users\zhang\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib/rustlib/src/rust\src\libstd/rt.rs:6716: std::rt::lang_start_internal::{{closure}}at src\libstd/rt.rs:5217: std::panicking::try::do_callat src\libstd/panicking.rs:29718: std::panicking::tryat src\libstd/panicking.rs:27419: std::panic::catch_unwindat src\libstd/panic.rs:39420: std::rt::lang_start_internalat src\libstd/rt.rs:5121: std::rt::lang_startat C:\Users\zhang\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib/rustlib/src/rust\src\libstd/rt.rs:6722: main23: _tmainCRTStartup24: mainCRTStartup25: _report_error26: _report_error
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
这个例子太于简单,调用堆栈都是Rust标准库的调用,没有多少有用的信息,如果真正的大型项目,相信还是有用的。
panic! 宏可可以被显式的调用:
fn main() {panic!("我要挂了");
}
可恢复的错误
在前面见到Resut枚举时,用了一个例子,打开一个不存在的文件:
use std::fs::File;fn main() {let f = File::open("hello.txt");match f {Ok(file) => println!("open file success"),Err(error) => println!("open file error:{:?}", error)};
}
File::open返回的是Resutl枚举,Result枚举实现实现了多个方法,如unwrap
和expect
,它们可以用来简化match:
use std::fs::File;fn main() {let f = File::open("hello.txt").unwrap();
}
在hello.txt不存在的情况下,运行时会有以下输出:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "系统找不
到指定的文件。" }', src\main.rs:4:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
unwrap
内部对open返回的Result进行了模式匹配,如果是Ok,则返回Ok的值,如果是Err,则直接调用panic!。我们希望能看到更多的信息,比如究竟是哪个文件找不到。这时,可以使用expect
。expect
允许在调用panic!时传入我们自己的信息:
use std::fs::File;fn main() {let f = File::open("hello.txt").expect("can not found file: 'hello.txt'");
}
这段代码输出:
thread 'main' panicked at 'can not found file: 'hello.txt': Os { code: 2, kind: NotFound, message: "系统找不到指定的文件
。" }', src\main.rs:4:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
从编程逻辑上,如果文件不存在,最好的方法是创建它。那么,打开文件失败就一定是文件不存在吗?当然不是,我们可以通过错误类型来判断:
use std::fs::File;
use std::io::ErrorKind;fn main() {let f = File::open("hello.txt");match f {Ok(file) => println!("open file success"),Err(error) => match error.kind() {ErrorKind::NotFound => println!("file not found"),_ => println!("open file error:{:?}", error)}};
}
前面我们已经知道File::open返回一个Result枚举,我们通过match进行模式匹配。error.kind()也返回一个ErrorKind枚举,我们可以通过模式匹配嵌套的方式进行处理。我们已经可以判断是不是因为文件不存在而失败了,再进一步,就可以进行创建文件了。
但是问题来了,创建就一定能成功吗?显示也是不行的,因此,创建文件的函数也返回一个Result枚举:
use std::fs::File;
use std::io::ErrorKind;fn main() {let f = File::open("hello.txt");match f {Ok(file) => println!("open file success"),Err(error) => match error.kind() {ErrorKind::NotFound => match File::create("hello.txt") {Ok(file) => println!("create file success"),Err(e) => println!("create file error: {}", e),},_ => println!("open file error:{:?}", error)}};
}
这段代码可以正常工作,可是三个match嵌套在一起看起来有些不太舒服。我们还可以使用unwrap
和expect
的变种,以unwrap
为例,它有4个变种:
unwrap_err:与
unwrap
,返回Err的值,当Result是Ok时panicunwrap_or(default):如果是Ok,则返回Ok的值,如果是Err返回给定的default
unwrap_or_default:如果是Ok,则返回Ok的值,如果是Err返回Ok相应类型的默认值
unwrap_or_else():传入一个闭包,如果是Ok,则返回Ok的值,如果是Err则执行这个闭包。我前面写过一篇关于闭包的文章,感兴趣的朋友可以看一下:https://blog.csdn.net/zhmh326/article/details/108443322。
use std::fs::File;
use std::io::ErrorKind;fn main() {let f = File::open("hello.txt").unwrap_or_else(|e| {if e.kind() == ErrorKind::NotFound {File::create("hello.txt").unwrap()} else {panic!("open file error:{:?}", e);}});
}
传播错误
try…catch…有很好的传播特性,当异常发生时,如果不进行处理,它会传播到调用者那里,由调用者处理。Rust的错误处理同样可以实现错误的传播,不过,它不像try…catch…那样是自动的,而是需要将Result类型作为返回值一层层的返回,直到被处理。
比如我们封装一个从文件中读取数据的函数:
use std::io;
use std::io::Read;
use std::fs::File;fn read_file(path: &str) -> Result<String, io::Error> {let mut f = match File::open(path) {Ok(file) => file,Err(e) => return Err(e),};let mut s = String::new();match f.read_to_string(&mut s) {Ok(_) => Ok(s),Err(e) => Err(e),}
}fn main() {println!("file content: {}", read_file("hello.txt").unwrap());
}
read_file函数返回了Result<String, io::Error>类型,在main调用时使用unwrap
对其进行处理。事实上,read_file并没有省略对Reuslt的处理,只是将它原样返回了出来。显然这种写法并不友好,这时,可以使用?
运算符:
use std::io;
use std::io::Read;
use std::fs::File;fn read_file(path: &str) -> Result<String, io::Error> {let mut f = File::open(path)?;let mut s = String::new();match f.read_to_string(&mut s) {Ok(_) => Ok(s),Err(e) => Err(e),}
}fn main() {println!("file content: {}", read_file("hello.txt").unwrap());
}
第6行调用File::open后,接了一个?
,不再使用match匹配Result,这样的写法如上面的例子是完全等价的,即当Result为Ok时将文件句柄返回给f,当为Err时,函数直接返回Err。read_to_string也可以这么做,甚至,这两次调用可以使用链式表达:
use std::io;
use std::io::Read;
use std::fs::File;fn read_file(path: &str) -> Result<String, io::Error> {let mut s = String::new();File::open(path)?.read_to_string(&mut s)?;Ok(s)
}fn main() {println!("file content: {}", read_file("hello.txt").unwrap());
}
当出现错误时,这两个?
都有可能返回Err,没有错误时,返回Ok(s)。
Rust的错误处理机制相关推荐
- Go语言的错误异常处理机制及其应用
一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...
- SpringBoot之错误处理机制
文章目录 1.SpringBoot默认的错误处理机制 2.错误处理原理 (1)DefaultErrorAttributes (2)BasicErrorController:处理默认的/error请求 ...
- Vue.js@2.6.10更新内置错误处机制,Fundebug同步支持相应错误监控
2019独角兽企业重金招聘Python工程师标准>>> 摘要: Fundebug 的 JavaScript 错误监控插件同步支持 Vue.js 异步错误监控. Vue.js 从诞生至 ...
- Laravel 5.5 的错误异常处理机制以及应用实例
一.前言 我们在开发项目中,难免会因为逻辑上的失误而报错,这些报错的展现形式也就是框架封装好的异常处理机制.在项目上线之前,我们还可以根据框架提供的报错信息来锁定错误代码的位置.但是项目上线之后我们是 ...
- .net错误处理机制
.net错误处理机制 让我们想想,ASP.NET为我们提供了几种错误处理机制?如果同时使用他们是不是有一定的优先级?.NET提供了四种错误处理机制,它们有一定的优先级顺序:Page_Error事件&g ...
- ASP.NET的错误处理机制
通常情况下,我们在写ASP.NET程序的时候,会经常遇到如何处理错误处理机制的问题.可以说一个良好的错误处理机制是衡量Web应用程序好坏的一个重要标准. 下面将介绍四中错误处理机制: 1.使用Web. ...
- Spring boot错误处理机制
错误处理机制 当程序发生错误的时候 浏览器访问 Spring boot提供了一个默认的错误页面 包括错误状态码.错误类型.提示消息.时间 客户端访问 当程序发生错误的时候 默认响应了一个json数据 ...
- golang错误处理机制(异常处理)
看一段代码,引入错误处理 对上面代码的总结: 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.) 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行.还可 以 ...
- springboot返回modelandview 找不到视图_SpringBoot错误处理机制及原理
SpringBoot错误信息处理机制 ★ 在一个web项目中,总需要对一些错误进行界面或者json数据返回,已实现更好的用户体验,SpringBoot中提供了对于错误处理的自动配置 " Er ...
- QTP3种错误处理机制
QTP 拥有3种不同的错误处理机制: 1.场景恢复机制 2.VBS的Error 对象 3.缺省的错误处理[Settings 里的runonError选项所指定的设置] 它们的优先级分别是: 1.场景恢 ...
最新文章
- VMware Coding Challenge: Possible Scores Summary: static
- B - Frogger POJ - 2253
- 初探Object Pascal的类(三)
- 创建springboot出现error:connection timed out创建springboot报错显示连接超时解决方案
- Touch事件UIControlEvents详解
- 如何将计算机加入到域环境中,如何在讲计算机加入一个WinXP的域环境
- python福利彩随机_看大神如何用Python分析福利彩票的秘密,百万大奖不是梦!
- 嵌入式电路设计(第一个商业pcb电路图绘制)
- linux find 命令详解
- yum配合rpm查看软件包安装位置
- Numpy的使用(4)
- [PaPaPa][需求说明书][V2.0]
- MP4视频播放器代码
- python 主函数传参数
- wordpress插件_5个最佳WordPress企业目录插件
- paypal如何退款
- SQL数据库被标为可疑/置疑/质疑的处理
- Java、统计正数和负数的个数然后计算这些数的平均值
- 23神经网络 :唐宇迪《python数据分析与机器学习实战》学习笔记
- 期货价值计算方法(期货的价值怎么计算)
热门文章
- php设计模式番外篇--超人的诞生
- gis怎么通过水库划分子流域_基于HEC-HMS模型推求西江流域巨型水库群区间入流...
- 什么是取整?有几种取整方式?C语言又是哪种方式?取模取余一样吗?
- ​ SequoiaDB 简介​,巨杉数据库整体介绍
- AD PCB布板提示The following exception occurred whilst loading section primitive paramenters...解决方法
- 基因测序的云计算平台可能带来的变革与进步
- Windows 2012重置系统管理员密码
- redis修改密码(windows)
- Dave Cheney去了Heptio
- 压缩文件不记得密码了怎么办?