错误处理
Rust大约 3 分钟...
Rust 的错误处理机制是其安全性和可靠性的重要组成部分,与许多其他语言不同,Rust 强制开发者显式地处理错误,这有助于创建更健壮的程序
Rust 将错误分为两大类:
- 可恢复错误 - 比如文件未找到等,⼀般需要将它们报告给用户并再次尝试进行操作
- 不可恢复错误 - 而不可恢复错误往往就是 bug 的另⼀种说法,比如尝试访问超出数组结尾的位置等
其他大部分的编程语言都没有刻意地区分这两种错误,而是通过异常之类的机制来统⼀处理它们
panic
panic!
用于处理不可恢复错误
fn main() {
panic!("燃烧吧!");
}
在发生 panic 时,程序默认会展开调用栈,这个过程可能会消耗大量资源。如果希望立即终止程序,可以在 Cargo.toml 中设置
[profile.release]
panic = 'abort'
Result
大部分错误没有严重到需要终止程序的地步,Result<T, E>
是 Rust 处理可恢复错误的主要方式,有两个变体:Ok
和Err
,比如在读取文件中
use std::fs::File;
fn main() {
let file = File::open("hello.txt");
match file {
Ok(file) => file,
Err(error) => {
panic!("{}", err)
}
}
}
无论如何,当 open 失败时都会触发 panic,但是想要根据不同的失败原因来做出处理反应,比如文件不存在时,可以创建这个文件并返回
use std::{fs::File, io::ErrorKind};
fn main() {
let file = File::open("hello.txt");
let file = match file {
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 therr was a problem: {:?}", e),
},
other_error => panic!("There was a problem opening the file: {:?}", other_error),
},
};
}
match 表达式确实有用,但可能用闭包更好一些,看起来阅读性更好
fn main() {
let file = 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 therr was a problem: {:?}", error)
});
} else {
panic!("There was a problem opening the file: {:?}", error);
}
});
}
方法
Result
类型本身也定义了很多方法用来应对,比如unwrap
方法,当返回值是 OK 则返回内部值,否则就会调用panic!
let file = File::open("hello.txt").unwrap();
还有一个expect
用于在unwrap
的基础上指定错误信息
let file = File::open("hello.txt").expect("Failed to open hello.txt");
这是一些其他的常用方法:
is_ok()
和is_err()
:返回 bool 值,告诉 Result 是成功的结果还是错误的结果ok()
:返回Option<T>
类型的成功值,否则返回None
err()
:返回Option<E>
类型的错误值unwrap_or(fallback)
:返回成功值,否则返回 fallback,丢弃错误值
? 运算符
Rust 提供了?
运算符用来传播错误,用来将错误返回给调用者,如果出现错误,就会提前终止函数执行,并返回错误。只能用于返回Result
类型的函数
fn read_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut s = String::new();
file.read_to_string(&mut s)?;
Ok(s)
}
自定义错误类型
对于复杂的应用,通常会定义自己的错误类型
use std::fmt;
use std::error::Error;
#[derive(Debug)]
struct AppError {
kind: String,
message: String,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.message)
}
}
impl Error for AppError {}
错误处理最佳实践
- 对于库代码,通常返回 Result,让调用者决定如何处理错误
- 对于应用代码,可以在 main 函数中使用 ? 运算符
- 使用 thiserror 或 anyhow 等 crate 来简化错误处理
- 为自定义错误类型实现 std::error::Error trait
- 使用有意义的错误消息,帮助用户或开发者理解和解决问题