Rust 语法学习 - 错误处理
Rust 将错误分为两大类:可恢复错误和不可恢复错误,提供了强大且灵活的错误处理机制。
7.1 不可恢复错误与 panic!
当遇到无法处理的情况时,Rust 程序可以通过 panic! 宏终止执行。
使用 panic!宏
fn main() {
panic!("发生了一个错误,程序崩溃");
}运行结果:
thread 'main' panicked at '发生了一个错误,程序崩溃', src/main.rs:2:5
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace设置 backtrace
设置环境变量 RUST_BACKTRACE=1 可以获取详细的堆栈跟踪:
$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running target/debug/my_project
thread 'main' panicked at '发生了一个错误,程序崩溃', src/main.rs:2:5
stack backtrace:
0: rust_begin_unwind
1: core::panicking::panic_fmt
2: my_project::main
3: core::ops::function::FnOnce::call_once
...自动 panic 的情况
一些常见会导致 panic 的情况:
- 数组越界访问:
let v = vec![1, 2, 3];
let value = v[99]; // 越界,会引发panic- 整数除零:
let x = 10;
let y = 0;
let z = x / y; // 除零,会引发panic- unwrap 空值:
let x: Option<i32> = None;
let value = x.unwrap(); // 对None调用unwrap,会引发panic控制 panic 行为
Rust 提供两种 panic 处理模式,可在 Cargo.toml 中配置:
[profile.release]
panic = 'abort' # 直接中止程序,不进行展开清理
# panic = 'unwind' # 默认行为,展开栈并清理数据7.2 Result 与可恢复错误
对于可恢复的错误,Rust 提供了 Result<T, E> 枚举:
enum Result<T, E> {
Ok(T), // 成功时返回的值
Err(E), // 错误时返回的值
}处理 Result
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let file_result = File::open("hello.txt");
// 使用match处理Result
let file = match file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("创建文件失败: {:?}", e),
},
other_error => panic!("打开文件失败: {:?}", other_error),
},
};
}使用闭包简化错误处理
use std::fs::File;
use std::io::ErrorKind;
fn main() {
// 使用闭包和方法简化上面的嵌套match
let file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("创建文件失败: {:?}", error);
})
} else {
panic!("打开文件失败: {:?}", error);
}
});
}错误传播
当函数可能失败时,通常返回 Result,让调用者决定如何处理:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = match File::open("hello.txt") {
Ok(file) => file,
Err(e) => return Err(e), // 错误传播给调用者
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e), // 错误传播给调用者
}
}unwrap 和 expect
unwrap 和 expect 是处理 Result 的简便方法,但在错误时会 panic:
use std::fs::File;
fn main() {
// unwrap: 成功则返回值,失败则panic
let file = File::open("hello.txt").unwrap();
// expect: 类似unwrap,但可以提供自定义panic消息
let file = File::open("hello.txt")
.expect("无法打开hello.txt文件");
}? 运算符
? 运算符简化了错误传播,功能类似于上面的 match 表达式:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_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)
}
// 更简洁的写法
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
// 最简洁的写法(使用标准库函数)
fn read_username_from_file() -> Result<String, io::Error> {
std::fs::read_to_string("hello.txt")
}? 运算符只能用于返回 Result 或 Option 的函数:
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
// 在main中使用?
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("hello.txt")?;
Ok(())
}7.3 错误处理指南
何时使用 panic!
- 示例代码、原型和测试中(简化)
- 当坏状态不是可能的
- 当你的代码处于无效状态,但不是因为调用者的输入引起的
- 当外部代码调用你的代码,提供了无法使用的值
- 当错误是不可恢复的
fn main() {
// 示例: 解析非用户输入的IP地址字符串,假设它总是有效的
let home = "127.0.0.1".parse().unwrap();
}何时返回 Result
- 当错误是可预期的
- 当调用者需要自己决定如何处理错误
- 当你实现一个可被广泛使用的函数或 API
fn get_weather(location: &str) -> Result<WeatherReport, WeatherError> {
// 实现...
}自定义错误类型
为库或应用创建自定义错误类型,提高错误处理能力:
use std::fmt;
use std::error::Error;
#[derive(Debug)]
enum AppError {
FileError(std::io::Error),
ParseError(std::num::ParseIntError),
InvalidInput(String),
}
// 实现Display trait以便于打印
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::FileError(e) => write!(f, "文件错误: {}", e),
AppError::ParseError(e) => write!(f, "解析错误: {}", e),
AppError::InvalidInput(s) => write!(f, "无效输入: {}", s),
}
}
}
// 实现Error trait使其成为真正的错误类型
impl Error for AppError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AppError::FileError(e) => Some(e),
AppError::ParseError(e) => Some(e),
AppError::InvalidInput(_) => None,
}
}
}
// 从io::Error转换为AppError
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
AppError::FileError(e)
}
}
// 从ParseIntError转换为AppError
impl From<std::num::ParseIntError> for AppError {
fn from(e: std::num::ParseIntError) -> Self {
AppError::ParseError(e)
}
}使用自定义错误类型:
fn read_and_parse() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // io::Error自动转换为AppError
let number: i32 = content.trim().parse()?; // ParseIntError自动转换为AppError
if number < 0 {
return Err(AppError::InvalidInput(format!("负数不被接受: {}", number)));
}
Ok(number)
}使用 thiserror 和 anyhow
Rust 生态系统中有许多库可以简化错误处理:
thiserror: 用于库中定义错误类型
use thiserror::Error;
#[derive(Error, Debug)]
enum DataStoreError {
#[error("数据查询失败")]
QueryError(#[from] sqlx::Error),
#[error("找不到ID为{0}的项目")]
NotFound(i64),
#[error("无效的数据: {0}")]
InvalidData(String),
}anyhow: 用于应用程序的简便错误处理
use anyhow::{Result, Context, bail, ensure};
fn read_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("无法读取配置文件 {}", path))?;
let config: Config = serde_json::from_str(&content)
.context("配置文件格式无效")?;
// 提前返回带有消息的错误
if config.timeout == 0 {
bail!("超时不能为0");
}
// 断言条件,失败时返回错误
ensure!(config.max_retries > 0, "最大重试次数必须大于0");
Ok(config)
}7.4 try 块(实验性功能)
Rust 正在开发 try 块功能,进一步简化错误处理:
// 未来可能的语法 (目前不可用)
fn read_and_process() -> Result<ProcessedData, MyError> {
try {
let file = File::open("data.txt")?;
let data = parse_data(file)?;
let processed_data = process(data)?;
processed_data
}
}7.5 实用错误处理模式
组合器模式
使用 map、and_then 等方法链式处理 Result:
fn process_data() -> Result<i32, Error> {
File::open("data.txt")
.map_err(|e| Error::IoError(e))
.and_then(|file| {
BufReader::new(file)
.lines()
.next()
.ok_or(Error::EmptyFile)
})
.and_then(|line_result| {
line_result.map_err(|e| Error::IoError(e))
})
.and_then(|line| {
line.parse::<i32>()
.map_err(|e| Error::ParseError(e))
})
}使用 collect 收集多个结果
collect 方法可以将 Vec<Result<T, E>> 转换为 Result<Vec<T>, E>:
fn process_items(items: Vec<String>) -> Result<Vec<i32>, std::num::ParseIntError> {
items
.iter()
.map(|s| s.parse::<i32>())
.collect()
}返回 impl Error 类型
对于通用错误处理,可以返回一个实现了 Error trait 的类型:
fn read_config() -> Result<Config, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("config.json")?;
let config = serde_json::from_str(&content)?;
Ok(config)
}