Option、Result与错误处理
错误处理是保证程序健壮性的前提,在编程语言中错误处理的方式大致分为两种:抛出异常(exceptions)和作为值返回。
Rust 将错误作为值返回并且提供了原生的优雅的错误处理方案。
熟练掌握错误处理是软件工程中非常重要的环节,让我一起来看看Rust展现给我们的错误处理艺术。
17.1 Option和Result
谨慎使用panic:
fn guess(n: i32) -> bool {
if n < 1 || n > 10 {
panic!("Invalid number: {}", n);
}
n == 5
}
fn main() {
guess(11);
}panic会导致当前线程结束,甚至是整个程序的结束,这往往是不被期望看到的结果。(编写示例或者简短代码的时候panic不失为一个好的建议)
Option
Option 是Rust的系统类型,用来表示值不存在的可能,这在编程中是一个好的实践,它强制Rust检测和处理值不存在的情况。例如:
find在字符串haystack中查找needle字符,事实上结果会出现两种可能,有(Some(usize))或无(None)。
Rust 使用模式匹配来处理返回值,调用者必须处理结果为None的情况。这往往是一个好的编程习惯,可以减少潜在的bug。Option 包含一些方法来简化模式匹配,毕竟过多的match会使代码变得臃肿,这也是滋生bug的原因之一。
unwrap
unwrap当遇到None值时会panic,如前面所说这不是一个好的工程实践。不过有些时候却非常有用:
在例子和简单快速的编码中 有的时候你只是需要一个小例子或者一个简单的小程序,输入输出已经确定,你根本没必要花太多时间考虑错误处理,使用
unwrap变得非常合适。当程序遇到了致命的bug,panic是最优选择
map
假如我们要在一个字符串中找到文件的扩展名,比如foo.rs中的rs, 我们可以这样:
我们可以使用map简化:
map如果有值Some(T)会执行f,反之直接返回None。
unwrap_or
unwrap_or提供了一个默认值default,当值为None时返回default:
and_then
看起来and_then和map差不多,不过map只是把值为Some(t)重新映射了一遍,and_then则会返回另一个Option。如果我们在一个文件路径中找到它的扩展名,这时候就会变得尤为重要:
Result
Result是Option的更通用的版本,比起Option结果为None它解释了结果错误的原因,所以:
这样的别名是一样的(()标示空元组,它既是()类型也可以是()值)
unwrap
没错和Option一样,事实上它们拥有很多类似的方法,不同的是,Result包括了错误的详细描述,这对于调试人员来说,这是友好的。
Result我们从例子开始
double_number从一个字符串中解析出一个i32的数字并*2,main中调用看起来没什么问题,但是如果把"10"换成其他解析不了的字符串程序便会panic
parse返回一个Result,但让我们也可以返回一个Option,毕竟一个字符串要么能解析成一个数字要么不能,但是Result给我们提供了更多的信息(要么是一个空字符串,一个无效的数位,太大或太小),这对于使用者是友好的。当你面对一个Option和Result之间的选择时。如果你可以提供详细的错误信息,那么大概你也应该提供。
这里需要理解一下FromStr这个trait:
number_str.parse::<i32>()事实上调用的是i32的FromStr实现。
我们需要改写这个例子:
不仅仅是map,Result同样包含了unwrap_or和and_then。也有一些特有的针对错误类型的方法map_err和or_else。
Result别名
在Rust的标准库中会经常出现Result的别名,用来默认确认其中Ok(T)或者Err(E)的类型,这能减少重复编码。比如io::Result
组合Option和Result
Option的方法ok_or:
可以在值为None的时候返回一个Result::Err(E),值为Some(T)的时候返回Ok(T),利用它我们可以组合Option和Result:
double_arg将传入的命令行参数转化为数字并翻倍,ok_or将Option类型转换成Result,map_err当值为Err(E)时调用作为参数的函数处理错误
复杂的例子
file_double从文件中读取内容并将其转化成i32类型再翻倍。 这个例子看起来已经很复杂了,它使用了多个组合方法,我们可以使用传统的match和if let来改写它:
这两种方法个人认为都是可以的,依具体情况来取舍。
try!
try!事实上就是match Result的封装,当遇到Err(E)时会提早返回, ::std::convert::From::from(err)可以将不同的错误类型返回成最终需要的错误类型,因为所有的错误都能通过From转化成Box<Error>,所以下面的代码是正确的:
组合自定义错误类型
CliError分别为io::Error和num::ParseIntError实现了From这个trait,所有调用try!的时候这两种错误类型都能转化成CliError。
总结
熟练使用Option和Result是编写 Rust 代码的关键,Rust 优雅的错误处理离不开值返回的错误形式,编写代码时提供给使用者详细的错误信息是值得推崇的。
Last updated
Was this helpful?