我们昨天学了panic
以及如何查看详细的报错信息。
今天我们继续往下学Result
recoverable
)错误和Result
昨天提到过了,有些场景是不需要panic
的,比如读取某个文件但是这个文件不存在,这个时候就没必要去崩溃,给个提示,或者给它生成一个新文件。
我们之前应该是用到过Result
这个枚举的,你应该还记得是一个枚举,有Ok
和Err
这两个变体(varant
),它们也是默认就prelude
的,所以不需要我们手动去引入。
T
自然是调用成功的返回值的类型,而E
则是错误场景的类型。
我们来写个例子,打开a.txt
文件
open
这个关联函数返回的是一个io::Result
的枚举
你会发现并没有报错,但是我们确实没这个文件,为什么呢?因为这段代码是runtime
调用时才会知道结果。我们来运行下
可以看到返回的错误数据是一个Os
的struct
也就是构造体。
如果处理这种场景相信大家都很熟悉了,没错就是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_else
的method
,这个方法主要功能是ok
直接返回value
,而error
场景则执行传入的闭包。
可以看到参数接收的是一个callback
,这个callback
接收e
这个数据作为参数,最后要求这个callback
返回的类型和Ok
的是一样的。
这么看,貌似和js
中的闭包效果差不多?这个问题留到学闭包的时候再说,先mark
起来。
unwrap
以及expect
我们直接来看下例子,先来看unwrap
的
来看下unwrap
的源码
这玩意儿一目了然,出错直接给你panic
,没问题返回value
。
然后我们再来看下expect
的例子
其实我们之前已经用过了,看下源码
没啥好说的,相对于unwrap
来说多了个自定义错误提示语的功能。
所以常规情况下用expect
即可。
propagating
)刚才我们的例子都是直接在match
的block
中直接处理的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
,e
为io: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+
提供的?
也是蛮像的,至少错误场景都不会直接报错崩溃,有个空值保护的空间。
这个量有点多老实说,没想到可恢复错误场景这块内容这么多。。。。
重点是这个?
操作符和错误传播这两个点,其他点都比较好理解。
最后,如果觉得这篇文章对你有帮助,请务必点个赞,谢谢~
编辑于 2022-12-13 12:23・IP 属地广东