文章目录

  • 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枚举实现实现了多个方法,如unwrapexpect,它们可以用来简化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!。我们希望能看到更多的信息,比如究竟是哪个文件找不到。这时,可以使用expectexpect允许在调用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嵌套在一起看起来有些不太舒服。我们还可以使用unwrapexpect的变种,以unwrap为例,它有4个变种:

  • unwrap_err:与unwrap,返回Err的值,当Result是Ok时panic

  • unwrap_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的错误处理机制相关推荐

  1. Go语言的错误异常处理机制及其应用

    一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...

  2. SpringBoot之错误处理机制

    文章目录 1.SpringBoot默认的错误处理机制 2.错误处理原理 (1)DefaultErrorAttributes (2)BasicErrorController:处理默认的/error请求 ...

  3. Vue.js@2.6.10更新内置错误处机制,Fundebug同步支持相应错误监控

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: Fundebug 的 JavaScript 错误监控插件同步支持 Vue.js 异步错误监控. Vue.js 从诞生至 ...

  4. Laravel 5.5 的错误异常处理机制以及应用实例

    一.前言 我们在开发项目中,难免会因为逻辑上的失误而报错,这些报错的展现形式也就是框架封装好的异常处理机制.在项目上线之前,我们还可以根据框架提供的报错信息来锁定错误代码的位置.但是项目上线之后我们是 ...

  5. .net错误处理机制

    .net错误处理机制 让我们想想,ASP.NET为我们提供了几种错误处理机制?如果同时使用他们是不是有一定的优先级?.NET提供了四种错误处理机制,它们有一定的优先级顺序:Page_Error事件&g ...

  6. ASP.NET的错误处理机制

    通常情况下,我们在写ASP.NET程序的时候,会经常遇到如何处理错误处理机制的问题.可以说一个良好的错误处理机制是衡量Web应用程序好坏的一个重要标准. 下面将介绍四中错误处理机制: 1.使用Web. ...

  7. Spring boot错误处理机制

    错误处理机制 当程序发生错误的时候 浏览器访问 Spring boot提供了一个默认的错误页面 包括错误状态码.错误类型.提示消息.时间 客户端访问 当程序发生错误的时候 默认响应了一个json数据 ...

  8. golang错误处理机制(异常处理)

    看一段代码,引入错误处理 对上面代码的总结: 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.) 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行.还可 以 ...

  9. springboot返回modelandview 找不到视图_SpringBoot错误处理机制及原理

    SpringBoot错误信息处理机制 ★ 在一个web项目中,总需要对一些错误进行界面或者json数据返回,已实现更好的用户体验,SpringBoot中提供了对于错误处理的自动配置 " Er ...

  10. QTP3种错误处理机制

    QTP 拥有3种不同的错误处理机制: 1.场景恢复机制 2.VBS的Error 对象 3.缺省的错误处理[Settings 里的runonError选项所指定的设置] 它们的优先级分别是: 1.场景恢 ...

最新文章

  1. CF510D Fox And Jumping(动态规划转换为最短路,O(n^2×2^9) -> O(nlogn),裴蜀定理应用)
  2. 【Beta版本】冲刺-Day6
  3. ASP.NET杂谈-一切都从web.config说起(2)(ConfigSections详解-上 )
  4. Rxjs debounce 操作符在 SAP Spartacus 函数节流中的一个实际使用例子
  5. Python学习之路:函数参数及调用
  6. 无法定位程序输入点dxgiget_美国ABB TZIDC 智能定位器调试方法
  7. 带格式化参数的strcat宏定义
  8. 计算机组成原理笔记第十章笔记整理
  9. SSH-远程登录协议
  10. java8新特性——Optional (1)
  11. solidworks工程图剖视图没有从默认的A开始解决办法
  12. session 的工作原理?
  13. XDOJ最长单词的长度
  14. 每个开发人员都应该学习的5种编程语言(上)
  15. HTML朗读可以用英文吗,关于英语朗读的方法技巧
  16. 色彩系列教程(3):实际运用
  17. 迅雷php源码,PHP生成迅雷、快车、旋风等软件的下载链接代码实例
  18. 在5G智慧园区的“保龄球道”上,目标全垒打的征途
  19. CentOS安装Redis教程
  20. 无需括号的xss payload

热门文章

  1. web期末大作业:基于html+css+js制作 学校班级网页制作我的校园
  2. 防火墙工作在哪个层_数据库安全关键技术之数据库防火墙技术
  3. 基于C#.NET三层架构物流运输管理系统(TMS)-C/S框架网原创作品
  4. Spring Cloud LoadBalanced 切换负载均衡策略
  5. “某某云词典” – 纠结的初体验
  6. 显卡内存足够但是torch报错RuntimeError: CUDA out of memory
  7. iSpiik产品说:谈谈小程序直播-数据背后几个思考
  8. java当前不可用,java代码向服务端狂发消息,导致的服务器连接通道不可用
  9. xxl子任务_XXL-JOB(1) 分布式任务系统选型和XXL-JOB介绍
  10. 关于头歌C/C++编程实训数组实训朋友圈点赞的一个题解