核心概念
-
Result<T, E>类型- 用于可能失败的操作,包含两个变体:
enum Result<T, E> { Ok(T), // 成功时携带结果 Err(E), // 失败时携带错误信息 } - 错误处理原则:不滥用
panic!,优先返回Result让调用者处理。
- 用于可能失败的操作,包含两个变体:
-
Option<T>类型- 用于可能缺失的值,包含两个变体:
enum Option<T> { Some(T), None, } - 适用场景:查找数据不存在(如哈希表查询),而非错误处理。
- 用于可能缺失的值,包含两个变体:
-
?运算符- 简化错误传播:若结果是
Err,则提前返回;否则解包Ok的值。 - 只能在返回
Result或Option的函数中使用。
- 简化错误传播:若结果是
-
自定义错误类型
- 实现
std::error::Errortrait,与Result结合使用。
- 实现
学习步骤
1. 基础错误处理
// 从文件读取内容,返回 Result
use std::fs::File;
use std::io::Read;
fn read_file(path: &str) -> Result<String, std::io::Error> {
let mut file = File::open(path)?; // 若打开失败,直接返回Err
let mut content = String::new();
file.read_to_string(&mut content)?; // 若读取失败,返回Err
Ok(content)
}
2. 组合 Option 和 Result
// 解析字符串为整数,处理可能的格式错误
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>()
}
fn main() {
let num_str = "42";
match parse_number(num_str) {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("解析失败: {}", e),
}
}
3. 自定义错误类型
use std::fmt;
// 自定义错误类型
#[derive(Debug)]
struct MyError {
message: String,
}
// 实现 Display 用于打印
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "自定义错误: {}", self.message)
}
}
// 实现 Error trait
impl std::error::Error for MyError {}
// 返回自定义错误的函数
fn check_value(value: i32) -> Result<(), MyError> {
if value < 0 {
Err(MyError {
message: format!("值 {} 不能为负数", value),
})
} else {
Ok(())
}
}
练习题
题目1:读取文件并处理错误
use std::fs::File;
use std::io::{self, Read};
fn read_file_contents(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn main() {
let path = "hello.txt";
match read_file_contents(path) {
Ok(text) => println!("文件内容:\n{}", text),
Err(e) => println!("读取文件失败: {}", e),
}
}
题目2:解析整数并返回 Result
fn parse_integer(input: &str) -> Result<i32, std::num::ParseIntError> {
input.parse::<i32>()
}
fn main() {
let inputs = ["42", "abc", "100"];
for s in inputs.iter() {
match parse_integer(s) {
Ok(n) => println!("解析成功: {} -> {}", s, n),
Err(e) => println!("解析失败: {} -> {}", s, e),
}
}
}
题目3:自定义错误整合
#[derive(Debug)]
enum MyError {
NegativeValue(i32),
TooLarge(i32),
}
impl std::error::Error for MyError {}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::NegativeValue(v) => write!(f, "值 {} 不能为负数", v),
MyError::TooLarge(v) => write!(f, "值 {} 超过最大值100", v),
}
}
}
fn validate_value(value: i32) -> Result<(), MyError> {
if value < 0 {
Err(MyError::NegativeValue(value))
} else if value > 100 {
Err(MyError::TooLarge(value))
} else {
Ok(())
}
}
fn main() {
let values = vec![-5, 50, 150];
for v in values {
match validate_value(v) {
Ok(()) => println!("值 {} 有效", v),
Err(e) => println!("错误: {}", e),
}
}
}
关键技巧
-
何时用
ResultvsOptionResult:操作可能失败,需报告错误原因(如文件不存在)。Option:数据可能不存在,不涉及错误(如查找键不存在)。
-
避免
unwrap()和expect()- 生产代码中尽量用
match或?处理错误,避免程序崩溃。 unwrap()会导致panic!,仅适用于原型快速开发。
- 生产代码中尽量用
-
错误类型转换
- 使用
map_err将错误类型转换为自定义错误:File::open("file.txt").map_err(|e| MyError::IoError(e))?;
- 使用
常见错误分析
错误示例:滥用 unwrap()
let num = "abc".parse::<i32>().unwrap(); // 运行时 panic!
- 修复:改用
match或?处理可能的Err。
错误示例:忽略 Result
let _ = File::open("file.txt"); // 未处理可能的错误
- 修复:至少用
if let检查结果:if let Ok(file) = File::open("file.txt") { // 处理文件 }
下一步任务
- 完成练习题,尝试在自定义错误中添加更多变体(如
FileReadError)。 - 阅读官方文档:错误处理