Rust的错误主要分可恢复(recoverable)和不可恢复(unrecoverable)两种,对于可恢复错误,只用报告问题并重试操作,而对于不可恢复错误则需要立即停止程序。可恢复错误通过Result类型来处理,不可恢复错误通过panic!宏来处理。
用panic!处理不可恢复的错误🔗
执行会造成代码panic的操作(越界访问数组)或调用panic!宏,都会造成程序panic,这是panic会打印错误信息,展开并清理栈数据然后退出。通过环境变量可以让Rust在panic时打印调用堆栈(call stack)以便定位panic的原因。
ℹ️ 注意
panic时也可选择直接终止(abort),不清理数据就退出,内存需要由操作系统清理。在Cargo.toml的[profile]添加panic = 'abort'即可,例如要在release模式下panic时直接终止,则:
[profile.release]
panic = 'abort'直接调用panic!宏:
fn main() {
panic!("crash and burn");
}
运行结果:
thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
这里可以看到panic的位置,也告诉我们可以使用RUST_BACKTRACE=1环境变量来显示backtrace。
使用panic!宏的backtrace🔗
将RUST_BACKTRACE环境变量设置为任何不是0的值来获取backtrace,输入RUST_BACKTRACE=1 cargo run运行程序。
需要获取带有带有这些信息的backtrace,需要启用debug标识,而在不使用--release标识编译时,debug标识默认是开启的。
用Result处理可恢复的错误🔗
Result类型表示可能成功(Ok)或失败(Err)的操作,Result类型是枚举,定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
考虑下面代码:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
}
File::open的返回值是Result<T, E>,当File::open成功时,greeting_file_result变量的值是Ok,其中Ok的值是File类型的文件句柄;当File::open失败时,greeting_file_result的值是Err,其中Err的值是std::io::Error类型的错误对象。
若要实现文件读取操作,考虑下面代码:
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
}
}
这里如果File::open成功,则greeting_file的值是File类型的文件句柄,否则程序会panic并打印错误信息,告诉我们哪里出错了。
匹配不同错误🔗
上述代码无论什么错误都会调用panic!宏,但有时我们希望对不同的错误采取不同的处理方式,考虑如下代码:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => panic!("Problem opening the file: {:?}", other_error),
},
}
}
这里如果File::open失败,则判断错误类型,如果错误类型是ErrorKind::NotFound,则尝试创建文件,如果创建文件也失败,则调用panic!宏,否则返回创建的文件句柄;如果错误类型不是ErrorKind::NotFound,则调用panic!宏。
ℹ️ 注意
处理Result类型时,除了match语句外,使用闭包(closure)会使代码更加简洁。
失败时panic的简写:unwarp和expect🔗
Result类型定义了unwarp方法,如果Result是Ok,则返回Ok中的值,否则调用panic!宏。expect方法与unwarp方法类似,但expect方法接受一个错误信息参数,当Result是Err时,expect方法会打印错误信息并调用panic!宏。
传播错误🔗
考虑下面代码:
use std::fs::File;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt")?;
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
这里如果成功,则返回OK中的值,否则返回同样的Err,这就是传播错误。
用?运算符传播错误🔗
考虑下面代码:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
在Result后加上?运算符,如果Result是Ok,则返回Ok中的值,否则返回Err,与上面的match表达式一致。
与match不用的是,?会将错误值传递给from函数,它定义于标准库的From trait中,用于将错误类型从一种转换为另一种,在有自定义的错误类型时可以节省代码。
在?之后使用链式方法调用进一步缩短代码:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
可见确实节省了很多代码。
下面还有更为简短的写法:
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
fs::read_to_string函数会打开文件,读取文件内容,然后将内容放入String中,并返回Result类型。
⚠️ 警告
?运算符只能用于返回Result或Option类型的函数,如果函数返回的是其他类型,则不能使用?运算符。
一般main函数返回(),修改后的main函数也可以使用?:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}总结🔗
本文讲解了Rust的错误处理,介绍了panic!宏和Result类型,并介绍了如何使用?运算符传播错误。