Rust通过 Result
枚举 和 panic!
宏来处理错误和异常情况。这种设计强调显式错误处理,避免了未捕获异常导致的程序意外终止。
1. Rust 错误处理的核心概念
Rust 将错误分为两类:
-
可恢复错误 :使用
Result<T, E>
枚举表示,程序可以从中恢复并继续运行 -
不可恢复错误 :使用
panic!
宏处理,会导致程序终止
Result
枚举定义如下:
rust
scss
enum Result<T, E> {
Ok(T), // 操作成功,包含结果值
Err(E), // 操作失败,包含错误信息
}
2. 可恢复错误处理详解
2.1 ?
运算符
- 用于传播错误,当
Result
为Err
时,自动返回该错误 - 相当于简化的
match
表达式:if let Err(e) = result { return Err(e); }
- 只能用于返回
Result
或Option
的函数中
2.2 自定义错误类型
- 通常使用枚举定义,可包含多种错误情况
- 实现
Display
和Error
trait 使其成为标准错误类型 - 可以包装其他错误类型(如示例中的
FileReadError(io::Error)
)
2.3 错误处理方法
-
完整匹配 :使用
match
表达式处理Ok
和Err
两种情况 -
简化处理 :使用
if let Err(e) = result
只处理错误情况 -
快速处理:
unwrap()
:成功时返回值,失败时panic!
expect(msg)
:类似unwrap()
,但可自定义错误消息unwrap_or(default)
:失败时返回默认值unwrap_or_else(f)
:失败时通过函数计算返回值
3. 不可恢复错误(panic!
)
panic!
宏用于处理程序无法恢复的错误:
- 触发时会打印错误消息、堆栈跟踪,并终止程序
- 适合处理逻辑错误(如违反不变式、无效状态)
- 生产环境中应尽量避免,除非确实无法恢复
4. 错误处理最佳实践
- 优先使用
Result
:对于可能失败的操作(如文件 IO、网络请求),返回Result
- 合理使用
panic!
:仅在程序处于无法恢复的错误状态时使用 - 定义自定义错误类型:使错误信息更具体,便于调用者处理
- 避免过度使用
unwrap()
:生产代码中应显式处理错误 - 错误链 :通过
source()
方法提供错误的层级关系,便于调试
rust
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
// 1. 自定义错误类型(实现Error trait)
#[derive(Debug)]
enum DataProcessingError {
FileReadError(io::Error), // 包装IO错误
ParseError(String), // 解析错误
ValidationError(String), // 验证错误
EmptyDataError, // 空数据错误
}
// 为自定义错误实现Display trait,用于打印错误信息
impl std::fmt::Display for DataProcessingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DataProcessingError::FileReadError(e) => write!(f, "文件读取错误: {}", e),
DataProcessingError::ParseError(msg) => write!(f, "解析错误: {}", msg),
DataProcessingError::ValidationError(msg) => write!(f, "验证错误: {}", msg),
DataProcessingError::EmptyDataError => write!(f, "数据为空"),
}
}
}
// 为自定义错误实现Error trait,使其成为标准错误类型
impl std::error::Error for DataProcessingError {}
// 2. 使用Result处理可恢复错误
// 读取文件内容
fn read_file_content(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // 使用?传播错误
let mut content = String::new();
file.read_to_string(&mut content)?; // 使用?传播错误
Ok(content)
}
// 解析数字
fn parse_number(s: &str) -> Result<i32, DataProcessingError> {
s.parse()
.map_err(|_| DataProcessingError::ParseError(format!("无法将'{}'解析为数字", s)))
}
// 验证数字是否为正数
fn validate_positive(n: i32) -> Result<i32, DataProcessingError> {
if n > 0 {
Ok(n)
} else {
Err(DataProcessingError::ValidationError(format!(
"数字{}必须为正数",
n
)))
}
}
// 处理数据(组合多个可能出错的操作)
fn process_data(file_path: &str) -> Result<i32, DataProcessingError> {
// 读取文件内容
let content = read_file_content(file_path)
.map_err(DataProcessingError::FileReadError)?;
// 检查内容是否为空
if content.trim().is_empty() {
return Err(DataProcessingError::EmptyDataError);
}
// 解析并验证数据
let number = parse_number(&content)?;
let validated = validate_positive(number)?;
Ok(validated)
}
// 3. 使用panic!处理不可恢复错误(程序无法继续运行的情况)
fn safe_division(a: i32, b: i32) -> i32 {
// 对于预期不会发生的错误,使用panic!
if b == 0 {
// panic!会打印错误信息并终止程序
panic!("除数不能为零!a={}, b={}", a, b);
}
a / b
}
// 4. 错误处理策略示例
fn handle_errors() {
// 示例1: 完整匹配Result处理所有情况
let file_result = read_file_content("example.txt");
match file_result {
Ok(content) => println!("文件内容: {}", content),
Err(e) => println!("处理文件时出错: {}", e),
}
// 示例2: 使用if let处理特定错误情况
if let Err(e) = process_data("numbers.txt") {
println!("数据处理失败: {}", e);
// 检查错误类型(向下转型)
if let Some(_) = e.source() {
println!(" 原因: {}", e.source().unwrap());
}
}
// 示例3: 使用unwrap和expect(适合原型开发)
// 注意: 生产代码中应避免使用,可能导致程序崩溃
let content = read_file_content("demo.txt").expect("读取demo.txt失败");
println!("demo.txt内容: {}", content);
// 示例4: 使用unwrap_or提供默认值
let config = read_file_content("config.ini").unwrap_or_else(|_| String::from("default=10"));
println!("配置: {}", config);
}
fn main() {
println!("=== 错误处理示例 ===");
handle_errors();
// 演示panic!(取消注释查看效果)
// println!("\n=== 演示panic! ===");
// safe_division(10, 0); // 这行会触发panic!
println!("\n程序正常结束");
}