错误分为可恢复错误与不可恢复错误。可恢复错误就是并不会影响整个程序执行的错误,如文件打开失败,那么重试即可。而不可恢复错误,就是发生了那么程序继续执行就会发生不可预知的错误,中止执行。

Rust提供了用于可恢复错误的Result<T,E>和用于中止程序执行的panic!宏。

不可恢复错误与panic!

panic!宏会打印一段错误信息,展开并清理调用栈,然后退出程序。

还可以设置立即终止程序,不进行清理工作,内存由操作系统回收。在Cargo.toml下的[profile]区域添加如下内容:

[profile]
panic = 'abort'

可以指定在发布模式中使用终止模式

[profile.release]
panic = 'abort'

执行cargo run时使用RUST_BACKTRACE=1来显示调用栈信息。这些回溯信息是在调试模式下打印的,因此不要使用--release标志。

可恢复错误与Result

Result枚举的定义如下:

enum Result<T,E> {
	Ok(T),
	Err(E),
}

其中Ok变体包含成功时的值,Err变体是出错时的错误。

打开一个文件,我们需要判断是否打开成功,示例如下:

fn main() {
	let f = File::open("hello.txt");
 
	let f = match f {
		Ok(file) => file,
		Err(error) => {
			// panic!不要求分支的返回值与其它能够正常返回的分支的值一样
			panic!("These was a problem opening the file: {:?}", 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!("Tried to create file but there was a problem: {:?}", e),            
	        },            
	        other_error => panic!("There was a problem opening the file: {:?}", other_error),        
	    },    
	};
}

Result<T,E>通过match表达式实现了许多接收闭包的方法。通过闭包可以将上面的代码改写为,简化嵌套的match表达式:

use std::fs::File;
use std::io::ErrorKind;
fn main() {
	let f = File::open("hello.txt").map_err(|error| {
		if error.kind() == ErrorKind::NotFound {
			File::create("hello.txt").unwrap_or_else(|error| {
				panic!("Tried to create file but there was a problem: {:?}", error);
			})
		} else {
			panic!("There was a problem opening file: {:?}", error);
		}
	});
}

失败触发panic的快捷方式:unwrap和expect

Result的返回值是Ok变体的时候,unwrap返回Ok内部的值。 当Result的返回值是Err变体的时候,unwrap会调用panic!宏。

示例:

use std::fs::File;
fn main() {
	let f = File::open("hello.txt").unwrap();
}

expect是在unwrap的基础上可以指定panic!附带的错误信息:

use std::fs::File;
fn main() {
	let f = File::open("hello.txt").expect("There was a problem opening file");
}

传播错误

对于函数内部的可恢复错误,可以将错误返回给调用者,由调用者决定如何处理错误。

use std::io::{self, Read};
use std::fs::File;
 
fn read_username_from_file() -> Result<String, io::Error> {
	let f = File::open("hello.txt");
	let f = match f {
		Ok(file) => file,
		Err(e) => return Err(e),
	};
 
	let mut s = String::new();
	match f.read_to_string(&mut s) {
		Ok(_) => Ok(s),
		Err(e) => Err(e),
	}
}

错误传播在Rust中非常常见,所以专门提供了一个?运算符来简化语法。

  • 如果Result的返回值是Ok,那么会将Ok中的值作为表达式的返回结果。
  • 如果Result的返回值是Err,那么就会将这个值作为结果返回,就像使用了return语句。 上面的代码可以改写为:
use std::io::{self, Read};
use std::fs::File;
 
fn read_username_from_file() -> Result<String, io::Error> {
	let f = File::open("hello.txt")?;
 
	let mut s = String::new();
	f.read_to_string(&mut s)?;
	Ok(s)
}

?接收的错误会隐式调用from函数(实现了From trait)转为返回值类型的错误。只要实现了能够转为返回类型的from函数,?都可以处理。