如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大。它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃。所以今天我们就来聊一聊Rust中如何处理程序错误,也就是所谓的“亡羊补牢”。

基础概念

在编程中遇到的非正常情况通常可以分为三类:失败、错误、异常。

Rust中用两种方式来消除失败:强大的类型系统和断言。

对于类型系统,熟悉Java的同学应该比较清楚。例如我们给一个接收参数为int的函数传入了字符串类型的变量。这是由编译器帮我们处理的。

关于断言,Rust支持6种断言。分别是:

assert!

assert_eq!

assert_ne!

debug_assert!

debug_assert_eq!

debug_assert_ne!

从名称我们就可以看出来这6种断言,可以分为两大类,带debug的和不带debug的,它们的区别就是assert开头的在调试模式和发布模式下都可以使用,而debug开头的只可以在调试模式下使用。再来解释每个大类下的三种断言,assert!是用于断言布尔表达式是否为true,assert_eq!用于断言两个表达式是否相等,assert_ne!用于断言两个表达式是否不相等。当不符合条件时,断言会引发线程恐慌(panic!)。

Rust处理异常的方法有4种:Option、Result、线程恐慌(Panic)、程序终止(Abort)。接下来我们对这些方法进行详细介绍。

Option

Option我们在Rust入坑指南:千人千构一文中我们进行过一些介绍,它是一种枚举类型,主要包括两种值:Some(T)和None,Rust也是靠它来避免空指针异常的。

在前文中,我们并没有详细介绍如何从Option中提取出T,其实最基本的,可以用match来提取。而我也在前文中给你提供了官方文档的链接,不知道你有没有看。如果还没来得及看也没有关系,我把我看到的一些方法分享给你。

这里介绍两种方法,一种是expect,另一种是unwrap系列的方法。我们通过一个例子来感受一下。

fn main() {

let a = Some("a");

let b: Option = None;

assert_eq!(a.expect("a is none"), "a");

assert_eq!(b.expect("b is none"), "b is none"); //匹配到None会引起线程恐慌,打印的错误是expect的参数信息

assert_eq!(a.unwrap(), "a"); //如果a是None,则会引起线程恐慌

assert_eq!(b.unwrap_or("b"), "b"); //匹配到None时返回指定值

let k = 10;

assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);// 与unwrap_or类似,只不过参数是FnOnce() -> T

assert_eq!(None.unwrap_or_else(|| 2 * k), 20);

}

这是从Option中提取值的方法,有时我们会觉得每次处理Option都需要先提取,然后再做相应计算这样的操作比较麻烦,那么有没有更加高效的操作呢?答案是肯定的,我从文档中找到了map和and_then这两种方法。

其中map方法和unwrap一样,也是一系列方法,包括map、map_or和map_or_else。map会执行参数中闭包的规则,然后将结果再封为Option并返回。

fn main() {

let some_str = Some("Hello!");

let some_str_len = some_str.map(|s| s.len());

assert_eq!(some_str_len, Some(6));

}

但是,如果参数本身返回的结果就是Option的话,处理起来就比较麻烦,因为每执行一次map都会多封装一层,最后的结果有可能是Some(Some(Some(...)))这样N多层Some的嵌套。这时,我们就可以用and_then来处理了。

利用and_then方法,我们就可以有如下的链式调用:

fn main() {

assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));

}

fn sq(x: u32) -> Option {

Some(x * x)

}

关于Option我们就先聊到这里,大家只需要记住,它可以用来处理空值,然后能够使用它的一些处理方法就可以了,实在记不住这些方法,也可以在用的时候再去文档中查询。

Result

聊完了Option,我们再来看另一种错误处理方法,它也是一个枚举类型,叫做Result,定义如下:

#[must_use = "this `Result` may be an `Err` variant, which should be handled"]

pub enum Result {

Ok(T),

Err(E),

}

实际上,Option可以被看作Result。从定义中我们可以看到Result有两个变体:Ok(T)和Err(E)。

Result用于处理真正意义上的错误,例如,当我们想要打开一个不存在的文件时,或者我们想要将一个非数字的字符串转换为数字时,都会得到一个Err(E)结果。

Result的处理方法和Option类似,都可以使用unwrap和expect方法,也可以使用map和and_then方法,并且用法也都类似,这里就不再赘述了。具体的方法使用细节可以自行查看官方文档。

这里我们来看一下如何处理不同类型的错误。

Rust在std::io模块定义了统一的错误类型Error,因此我们在处理时可以分别匹配不同的错误类型。

use std::fs::File;

use std::io::ErrorKind;

fn main() {

let f = File::open("hello.txt");

let f = match f {

Ok(file) => file,

Err(error) => match error.kind() {

ErrorKind::NotFound => match File::create("hello.txt") {

Ok(fc) => fc,

Err(e) => panic!("Problem creating the file: {:?}", e),

},

ErrorKind::PermissionDenied => panic!("Permission Denied!"),

other_error => panic!("Problem opening the file: {:?}", other_error),

},

};

}

在处理Result时,我们还有一种处理方法,就是try!宏。它会使代码变得非常精简,但是在发生错误时,会将错误返回,传播到外部调用函数中,所以我们在使用之前要考虑清楚是否需要传播错误。

对于上面的代码,使用try!宏就会非常精简。

use std::fs::File;

fn main() {

let f = try!(File::open("hello.txt"));

}

try!使用起来虽然简单,但也有一定的问题。像我们刚才提到的传播错误,再就是有可能出现多层嵌套的情况。因此Rust引入了另一个语法糖来代替try!。它就是问号操作符“?”。

use std::fs::File;

use std::io;

use std::io::Read;

fn main() {

read_username_from_file();

}

fn read_username_from_file() -> Result {

let mut f = File::open("hello.txt")?;

let mut s = String::new();

f.read_to_string(&mut s)?;

Ok(s)

}

问号操作符必须在处理错误的代码后面,这样的代码看起来更加优雅。

恐慌(Panic)

我们从最开始就聊到线程恐慌,那道理什么是恐慌呢?

在Rust中,无法处理的错误就会造成线程恐慌,手动执行panic!宏时也会造成恐慌。当程序执行panic!宏时,会打印相应的错误信息,同时清理堆栈并退出。但是栈回退和清理会花费大量的时间,如果你想要立即终止程序,可以在Cargo.toml文件中[profile]区域中增加panic = ‘abort‘,这样当发生恐慌时,程序会直接退出而不清理堆栈,内存空间都由操作系统来进行回收。

程序报错时,如果你想要查看完整的错误栈信息,可以通过设置环境变量RUST_BACKTRACE=1的方式来实现。

如果程序发生恐慌,我们前面所说的Result就不能使用了,Rust为我们提供了catch_unwind方法来捕获恐慌。

use std::panic;

fn main() {

let result = panic::catch_unwind(|| {panic!("crash and burn")});

assert!(result.is_err());

println!("{}", 1 + 2);

}

在上面这段代码中,我们手动执行一个panic宏,正常情况下,程序会在第一行退出,并不会执行后面的代码。而这里我们用了catch_unwind方法对panic进行了捕获,结果如图所示。

Rust虽然打印了恐慌信息,但是并没有影响程序的执行,我们的代码println!("{}", 1 + 2);可以正常执行。

总结

至此,Rust处理错误的方法我们已经基本介绍完了,为什么说是基本介绍完了呢?因为还有一些大佬开发了一些第三方库来帮助我们更加方便的处理错误,其中比较有名的有error-chain和failure,这里就不做过多介绍了。

通过本节的学习,相信你的Rust程序一定会变得更加健壮。

丅rust是什么意思_Rust入坑指南:亡羊补牢相关推荐

  1. rust的矿坑_Rust入坑指南:鳞次栉比

    很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...

  2. python入坑指南_Rust入坑指南:万物初始

    有没有同学记得我们一起挖了多少个坑?嗯-其实我自己也不记得了,今天我们再来挖一个特殊的坑,这个坑可以说是挖到根源了--元编程. 元编程是编程领域的一个重要概念,它允许程序将代码作为数据,在运行时对代码 ...

  3. Rust 入坑指南:鳞次栉比 | CSDN 博文精选

    作者 | Jackyzhe 责编 | 屠敏 出品 | CSDN(ID:CSDNnews) 很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉 ...

  4. Rust入坑指南:齐头并进(上)

    我们知道,如今CPU的计算能力已经非常强大,其速度比内存要高出许多个数量级.为了充分利用CPU资源,多数编程语言都提供了并发编程的能力,Rust也不例外. 聊到并发,就离不开多进程和多线程这两个概念. ...

  5. Rust入坑指南:朝生暮死

    今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...

  6. Rust入坑指南:亡羊补牢

    如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...

  7. Rust入坑指南:鳞次栉比

    很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...

  8. Rust入坑指南:核心概念

    如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...

  9. 发布开源框架到CocoaPods入坑指南

    个人原文博客地址: 发布开源框架到CocoaPods入坑指南 在开发过程中一定会用到一些第三方框架, 只要安装了CocoaPods, 然后通过pod install命令, 就可以集成框架到项目中了 可 ...

最新文章

  1. DRX不连续接收(1)
  2. [dts]Device Tree机制【转】
  3. linux之通过strings命令查看so里面是否包含****字符串
  4. java虚拟机和javaGC_Java虚拟机(三):GC算法和种类
  5. 关于未捕获异常的处理(WPF)
  6. Transformer如何并行化? self-attention公式中的归一化有什么作用?
  7. 【BZOJ5338】[TJOI2018]异或(主席树)
  8. 请假系统特例规则详细设计
  9. 多麦克风做拾音的波束_【技术交流】音控未来——进击的麦克风阵列技术
  10. ParaView Volume MHD
  11. 商业逻辑12讲之领导力的逻辑
  12. 【解决】npm ERR A complete log of this run can be found in npm ERR
  13. 应用/游戏在三星Galaxy S8及S8+上的适配办法
  14. c语言规定 程序中各函数之间().,C语言基础笔试题
  15. java获取工作日 日历接口_节假日api接口之获取指定日期的节假日信息
  16. 关于input自动过滤特殊字符的简单方法
  17. postfix smtpd_recipient_restrictions配置错误导致smtpd问题
  18. 802.11n-技术概览
  19. 破冰船是怎么破冰的?和你想到一点不一样,6米高的冰墙直接就撞
  20. 如何给多个pdf批量加水印?

热门文章

  1. 降低电商快递运输成本的6种策略
  2. Windows照片查看器无法显示此照片,因为计算机上的可用内存可能不足
  3. 3.2 搞懂小红书算法运营逻辑,只需要5分钟【玩赚小红书】
  4. CNCC 2017大会第一天,邱成桐,梅宏,沈向洋,李飞飞,汤道生,马维英都讲了什么?...
  5. MDT2008部署杂谈2——简介
  6. A session ended very soon after starting. Check that the command in profile解决方法
  7. 出现-nan(ind)的情况
  8. u3d 巧用 CaptureScreenshot捕捉游戏画面(截图,截屏)
  9. CUDA与OpenCL架构
  10. 顶级 Swift 服务端框架对决 Node.js