4.7 错误处理
有时候程序会发生错误.对于不可避免的事情发生时最好有一个计划来处理.Rust有丰富的处理错误的方法.
你的程序会出现两种类型的错误:失败和崩溃.我们先讨论两者的区别,然后讨论如何处理他们.然后,我们讨论把错误升级为崩溃.
Page 107
4.7.1 失败和崩溃
Rust使用两种错误的类型:失败和崩溃.失败是一种可以被挽回的错误.崩溃却不行.
"挽回"是什么意思呢?在多数情况下,很有可能会发生错误.例如,一个parse函数:
"5".parse();
这个方法将一个字符转换为另一个类型.但是因为他是字符,你不能保证这个转换可以正常工作.例如,它该被转换成什么类型呢?
"hello5world".parse();
这个就不能工作.所以我们知道这个函数只能在某些输入下才能工作.这是可以预期的行为.我们称这种错误是失败(failure).
另一方面,有时候,我们会遇到不可预期的错误,或者我们没法挽回的错误.一个经典的assert!的例子:
assert!(x == 5);
我们使用assert!来声明一个表达式是true.如果不是,那么就有错误发生.这个错误导致我们无法继续当前的状态.另一个例子是使用unreachable!()宏:
enum Event {
NewRelease,
}
fn probability(_: &Event) -> f64 {
// real implementation would be more complex, of course
0.95
}
fn descriptive_probability(event: Event) -> &‘static str {
match probability(&event) {
1.00 => "certain",
0.00 => "impossible",
0.00 ... 0.25 => "very unlikely",
0.25 ... 0.50 => "unlikely",
0.50 ... 0.75 => "likely",
0.75 ... 1.00 => "very likely",
}
}
fn main() {
std::io::println!(descriptive_probability(NewRelease));
}
我们得到了一个错误:
虽然我们知道我们覆盖了所有的可能,但是Rust不能.他不知道可能区间是从0.0到1.0.所以我们需要加一个case:
use Event::NewRelease;
enum Event {
NewRelease,
}
fn probability(_: &Event) -> f64 {
// real implementation would be more complex, of course
0.95
}
fn descriptive_probability(event: Event) -> &‘static str {
match probability(&event) {
1.00 => "certain",
0.00 => "impossible",
0.00 ... 0.25 => "very unlikely",
0.25 ... 0.50 => "unlikely",
0.50 ... 0.75 => "likely",
0.75 ... 1.00 => "very likely",
_ => unreachable!()
}
}
fn main() {
println!("{}", descriptive_probability(NewRelease));
}
我们不必一直增加这个_case,所以我们使用unreachable!()宏来指出这一点.它是另一种错误处理的方式.Rust称这种错误为panic.
4.7.2 使用Option和Result来处理错误
一个最简单的处理函数失败的方法就是使用Option<T>类型.例如,find方法在字符串中试图找到一个匹配的串,然后返回一个Option:
let s = "foo";
assert!(s.find(‘f‘), Some(0));
assert!(s.find(‘z‘), None);
这基本就是最简单的方法了,但是这样的话我们没法得知错误信息.如果我们想要知道为什么失败了呢?为此,我们需要使用Result<T,E>类型.像这样:
enum Result<T, E> {
Ok(T),
Err(E)
}
这个枚举有Rust语言自身提供了,所以你不必定义它.Ok(T)变量代表成功,Err(E)变量代表失败.返回一个Result类型而不是Option类型是推荐的方法,除非你不关心失败信息.
有一个列子:
#[derive(Debug)]
enum Version { Version1, Version2 }
#[derive(Debug)]
enum ParseError { InvalidHeaderLength, InvalidVersion }
fn parse_version(header: &[u8]) -> Result<Version, ParseError> {
if header.len() < 1 {
return Err(ParseError::InvalidHeaderLength);
}
match header[0] {
1 => Ok(Version::Version1),
2 => Ok(Version::Version2),
_ => Err(ParseError::InvalidVersion)
}
}
let version = parse_version(&[1, 2, 3, 4]);
match version {
Ok(v) => {
println!("working with version: {:?}", v);
}
Err(e) => {
println!("error parsing header: {:?}", e);
}
}
这个函数使用了一个枚举, ParseError, 来枚举不同的错误.Debug特性可以是我们通过{:?}格式操作符来打印枚举值.
4.7.3 panic!不可恢复错误
当非预期的或不可恢复的错误发生时,panic!宏触发一个崩溃.它会让当前线程崩溃然后给出一个错误:
panic!("boom");
输出
thread ‘<main>‘ panicked at ‘boom‘, hello.rs:2
因为这种情况很少发生,所以谨慎的使用它.
4.7.4 将失败升级为崩溃
在某些情况下,甚至当一个函数失败的时候,我们希望把它当作已给崩溃而不是失败来处理.例如,io::stdin().read_line(&mut buffer)返回了一个Result<usize>,当读取一行输入失败的时候发生.这是我们可以处理这个失败并且恢复它.
如果我们不想处理这个错误,我们就想要终止程序运行,我们可以试用unwrap()方法.
io::stdin().read_line(&mut buffer).unwrap();
当Result是一个Err时,unwrap()会panic!.这意味着"给我值,如果那里出错了,让程序崩溃."这比起尝试恢复程序来说有点不靠谱,但是却更简洁.有时候,崩溃就对了.
另一种方法比unwrap()要好一点儿:
let mut buffer = String::new();
let input = io::stdin().read_line(&mut buffer)
.ok()
.expect("Failed to read line");
ok()把Result转化为Option,expect()对unwrap()做了同样的事,但是包含一条信息.这条信息传给了潜在的panic!,它提供了一个更好的带有错误信息的错误.
Page 111
4.7.5 使用try!
当调用多个函数都返回Result类型的时候,错误处理写起来会很繁琐.使用try!宏可以隐藏那些在调用栈上弥漫开来的错误信息.
把下述代码:
use std::fs::File;
use std::io;
use std::io::prelude::*;
struct Info {
name: String,
age: i32,
rating: i32,
}
fn write_info(info: &Info) -> io::Result<()> {
let mut file = File::create("my_best_friends.txt").unwrap();
if let Err(e) = writeln!(&mut file, "name: {}", info.name) {
return Err(e)
}
if let Err(e) = writeln!(&mut file, "age: {}", info.age) {
return Err(e)
}
if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {
return Err(e)
}
return Ok(());
}
替换为:
use std::fs::File;
use std::io;
use std::io::prelude::*;
struct Info {
name: String,
age: i32,
rating: i32,
}
fn write_info(info: &Info) -> io::Result<()> {
let mut file = try!(File::create("my_best_friends.txt"));
try!(writeln!(&mut file, "name: {}", info.name));
try!(writeln!(&mut file, "age: {}", info.age));
try!(writeln!(&mut file, "rating: {}", info.rating));
return Ok(());
}
将代码包裹在try!中会导致成功值,除非Result是Err,函数将会提前返回.
需要注意的是你只能在一个函数返回Result的时候使用try!,也就是说你不能在main()函数中使用try!,因为main()函数不返回任何值.
try!使用了From<Error>来决定返回什么错误.