坏蛋Dan
知乎@坏蛋Dan
发布时间:2024.1.3

前言

我们昨天学了panic以及如何查看详细的报错信息。

坏蛋Dan:rust基础学习--day18

今天我们继续往下学Result


可恢复的(recoverable)错误和Result

昨天提到过了,有些场景是不需要panic的,比如读取某个文件但是这个文件不存在,这个时候就没必要去崩溃,给个提示,或者给它生成一个新文件。

我们之前应该是用到过Result这个枚举的,你应该还记得是一个枚举,有OkErr这两个变体(varant),它们也是默认就prelude的,所以不需要我们手动去引入。

T自然是调用成功的返回值的类型,而E则是错误场景的类型。

我们来写个例子,打开a.txt文件

open这个关联函数返回的是一个io::Result的枚举

你会发现并没有报错,但是我们确实没这个文件,为什么呢?因为这段代码是runtime调用时才会知道结果。我们来运行下

可以看到返回的错误数据是一个Osstruct也就是构造体。

如果处理这种场景相信大家都很熟悉了,没错就是match,我们来改下我们的例子

这个时候就不会报错啦

但是我们还可以优化下,当找不到文件的时候给它创建一个文件,这会使用到File::create这个关联函数。

匹配不同的错误

在我们使用File::create之前,我们需要知道是什么错误类型才行,因为有可能是没有读的权限导致打开失败,这种时候是不能继续往下执行的,应该panic!掉。

这就需要用到Error的一个method--kind,会返回错误类型,来看下代码

可以看到我们使用了一个io里的ErrorKind这一个枚举,简单的看下部分数据

可以看到第一个就是NotFound

然后我们调用了error里的kind这个方法,这个方法返回了ErrorKind中的变体。

我们只需要NotFound这一个变体,所以其他场景就忽略了。

然后调用File::create这一关联函数创建文件,当然,创建文件也是有可能会出问题的,所以这里又来了个match处理错误场景。

最后cargo run一下

确实生成了。

现在我们的代码无懈可击,但是也是乱的一批,三层match套娃会让人觉得恶心,所以rust针对这种场景提供了类似的method,我们来看下。

这里面用到了闭包(closures)这一概念,我们后面会学到。两块代码实际效果是一样的,但是利用闭包的这种会更优雅些。

这里还用到了unwrap_or_elsemethod,这个方法主要功能是ok直接返回value,而error场景则执行传入的闭包。

可以看到参数接收的是一个callback,这个callback接收e这个数据作为参数,最后要求这个callback返回的类型和Ok的是一样的。

这么看,貌似和js中的闭包效果差不多?这个问题留到学闭包的时候再说,先mark起来。


一些简写:unwrap以及expect

我们直接来看下例子,先来看unwrap

来看下unwrap的源码

这玩意儿一目了然,出错直接给你panic,没问题返回value

然后我们再来看下expect的例子

其实我们之前已经用过了,看下源码

没啥好说的,相对于unwrap来说多了个自定义错误提示语的功能。

所以常规情况下用expect即可。


错误传递(propagating)

刚才我们的例子都是直接在matchblock中直接处理的Err场景,但有时候把错误抛出来会更有用,我们来看下例子

可以看到我们return(我之前好像说rust里没有return?如果说了当我没说)了一个Err(e),这个Err(e)中的e的类型是io:Error,然后我们看到调用了read_to_string这个方法,将数据写入username这个变量中, 但是read_to_string这个方法可能也会失败,所以这里又需要match一次。

上面说过它们(Result、Ok、Err)都是prelude的,所以这里直接调用Err是没问题的。

注意最后一个match语句并没有;,也就是说这个结果将作为函数的返回值。

还需要注意的一点是最后这个函数是Ok(_) => Ok(username)以及Err(e) => Err(e)

那么函数将有两种可能,Ok或者Err 这两种带值的变体,那么自然类型就会是一个Result,而T则是username,eio:Error

这样我们就结果交由开发者自己了。

但是上面这样的操作老实说还是有些麻烦,rust官方也提供了简写的方式。


使用?来简写错误传递

我们直接来看下例子

?操作符跟在了open方法后面,这样写相当于是unwrap方法,但是错误时返回Err(e),和刚才的代码也是有差别的,刚才我们的代码成功的时候返回的是Ok(username),而使用?操作符并不会返回Ok(username),而是直接返回value。这也就是为什么最后需要手动Ok(username)

可以看到代码明显少了很多,结构也更扁平了。然而上面的代码其实还是可以再优化的,因为username_file这个临时变量可以不用写,直接链式调用。

这样就优雅啦~

但是,其实还有更更优雅的

我们直接调用的fs模块的read_to_string关联函数,它返回的类型正好就是Result

是不是非常优雅?当然,如果你不想要io:Error这个错误类型,那只能是自己写了,这玩意儿只适用于这场景。

?是所有时候都能用的吗?并不是


哪些可以用?操作符

?操作符仅支持返回类型为Result/Option以及实现了FromResidual的类型这三种类型。

来看个例子

我们是直接在main函数里使用的,而main函数默认是()空元组,那要怎么办呢?

其实main函数也是可以改变返回类型的,我们来看下例子

表示允许接收任何错误类型,这个暂时不说啥原理,以后会学到。

这种错误传递的场景同样适用于Option,和Result一样,函数的返回值也一定得是Option,我们来看个例子

其中lines表示读取行,遇到\n就算一行,next表示迭代,和es6+iterator差不多。

简单地说这个last_char_of_first_line方法是获取每一行的最后一个字符,返回的是Some(char)或者None这俩变体其中一个,所以得是Option的类型。毕竟行的时候可能读不到。

总的来说,这个?es6+提供的?也是蛮像的,至少错误场景都不会直接报错崩溃,有个空值保护的空间。


总结

这个量有点多老实说,没想到可恢复错误场景这块内容这么多。。。。

重点是这个?操作符和错误传播这两个点,其他点都比较好理解。

最后,如果觉得这篇文章对你有帮助,请务必点个赞,谢谢~

参考

  1. ^rust-recoverable-error-and-result https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#recoverable-errors-with-result
  2. ^rust-matching-on-different-errors https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#matching-on-different-errors
  3. ^rust-shortcut-use-unwrap-or-expect https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#shortcuts-for-panic-on-error-unwrap-and-expect
  4. ^rust-propagating-errors https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#propagating-errors
  5. ^rust-shortcut-for-prepagating-errors-with-?-operator https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
  6. ^rust-when-can-use-?-operator https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#where-the--operator-can-be-used

编辑于 2022-12-13 12:23・IP 属地广东