Rust 错误处理
概述
Rust 采用了独特的错误处理方式,通过类型系统来处理错误,而不是使用异常机制。本章将学习 Rust 中的错误处理模式,包括 Result 和 Option 类型,以及错误传播和自定义错误类型。
🎯 Rust 错误处理哲学
错误的分类
rust
// Rust 将错误分为两类:
// 1. 可恢复错误(Recoverable Errors)- 使用 Result<T, E>
// 2. 不可恢复错误(Unrecoverable Errors)- 使用 panic!
fn error_classification() {
// 可恢复错误示例
let result = std::fs::read_to_string("maybe_exists.txt");
match result {
Ok(content) => println!("文件内容: {}", content),
Err(error) => println!("读取文件失败: {}", error),
}
// 不可恢复错误示例
let numbers = vec![1, 2, 3];
// let index = numbers[10]; // 这会 panic!
// 手动触发 panic
if numbers.is_empty() {
panic!("向量不能为空!");
}
}📦 Option 类型
Option 基础
rust
fn option_basics() {
// Option 表示可能存在或不存在的值
let some_number = Some(5);
let some_string = Some("字符串");
let absent_number: Option<i32> = None;
println!("有值: {:?}", some_number);
println!("无值: {:?}", absent_number);
// 使用 match 处理 Option
let x = Some(5);
match x {
Some(value) => println!("值是: {}", value),
None => println!("没有值"),
}
// 使用 if let 简化
if let Some(value) = some_number {
println!("找到值: {}", value);
}
}Option 的常用方法
rust
fn option_methods() {
let x = Some(2);
let y: Option<i32> = None;
// is_some() 和 is_none()
println!("x 有值吗? {}", x.is_some());
println!("y 没有值吗? {}", y.is_none());
// unwrap() - 危险方法,可能 panic
// let value = y.unwrap(); // 这会 panic!
// unwrap_or() - 提供默认值
let default_value = y.unwrap_or(0);
println!("默认值: {}", default_value);
// unwrap_or_else() - 使用闭包计算默认值
let computed_default = y.unwrap_or_else(|| {
println!("计算默认值");
42
});
println!("计算的默认值: {}", computed_default);
// expect() - 带自定义错误消息的 unwrap
let value = x.expect("x 应该有值");
println!("期望的值: {}", value);
// map() - 转换 Option 中的值
let doubled = x.map(|v| v * 2);
println!("翻倍: {:?}", doubled);
// and_then() - 链式操作
let result = x.and_then(|v| {
if v > 0 {
Some(v * 10)
} else {
None
}
});
println!("链式结果: {:?}", result);
// filter() - 条件过滤
let filtered = x.filter(|&v| v > 1);
println!("过滤结果: {:?}", filtered);
}Option 的实际应用
rust
fn find_user_by_id(id: u32) -> Option<User> {
let users = vec![
User { id: 1, name: "Alice".to_string() },
User { id: 2, name: "Bob".to_string() },
User { id: 3, name: "Charlie".to_string() },
];
users.into_iter().find(|user| user.id == id)
}
#[derive(Debug)]
struct User {
id: u32,
name: String,
}
fn option_real_world() {
// 查找用户
match find_user_by_id(2) {
Some(user) => println!("找到用户: {:?}", user),
None => println!("用户不存在"),
}
// 解析字符串
let number_str = "42";
let parsed = number_str.parse::<i32>().ok(); // Result 转 Option
println!("解析结果: {:?}", parsed);
// 数组访问
let numbers = vec![1, 2, 3, 4, 5];
let first = numbers.get(0);
let out_of_bounds = numbers.get(10);
println!("第一个元素: {:?}", first);
println!("越界访问: {:?}", out_of_bounds);
}⚠️ Result 类型
Result 基础
rust
// Result<T, E> 表示可能成功或失败的操作
fn divide(dividend: f64, divisor: f64) -> Result<f64, String> {
if divisor == 0.0 {
Err("不能除以零".to_string())
} else {
Ok(dividend / divisor)
}
}
fn result_basics() {
let success_result = divide(10.0, 2.0);
let error_result = divide(10.0, 0.0);
// 使用 match 处理 Result
match success_result {
Ok(value) => println!("结果: {}", value),
Err(error) => println!("错误: {}", error),
}
match error_result {
Ok(value) => println!("结果: {}", value),
Err(error) => println!("错误: {}", error),
}
// 使用 if let 简化
if let Ok(value) = divide(20.0, 4.0) {
println!("除法结果: {}", value);
}
if let Err(error) = divide(10.0, 0.0) {
println!("除法错误: {}", error);
}
}Result 的常用方法
rust
fn result_methods() {
let success: Result<i32, &str> = Ok(42);
let failure: Result<i32, &str> = Err("出错了");
// is_ok() 和 is_err()
println!("成功吗? {}", success.is_ok());
println!("失败吗? {}", failure.is_err());
// unwrap() 和 expect()
let value = success.unwrap();
println!("解包的值: {}", value);
// let error_value = failure.unwrap(); // 这会 panic!
let expected_value = success.expect("应该是成功的");
println!("期望的值: {}", expected_value);
// unwrap_or() 和 unwrap_or_else()
let default_value = failure.unwrap_or(0);
println!("默认值: {}", default_value);
let computed_value = failure.unwrap_or_else(|error| {
println!("处理错误: {}", error);
-1
});
println!("计算值: {}", computed_value);
// map() 和 map_err()
let doubled = success.map(|v| v * 2);
println!("翻倍结果: {:?}", doubled);
let mapped_error = failure.map_err(|e| format!("错误: {}", e));
println!("映射错误: {:?}", mapped_error);
// and_then() - 链式操作
let chained = success.and_then(|v| {
if v > 0 {
Ok(v.to_string())
} else {
Err("值必须大于0")
}
});
println!("链式结果: {:?}", chained);
}🔄 错误传播
? 运算符
rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?; // ? 运算符
let mut s = String::new();
f.read_to_string(&mut s)?; // ? 运算符
Ok(s)
}
// 等价的 match 版本
fn read_username_from_file_verbose() -> Result<String, io::Error> {
let f = File::open("username.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
// 更简洁的版本
fn read_username_from_file_short() -> Result<String, io::Error> {
let mut s = String::new();
File::open("username.txt")?.read_to_string(&mut s)?;
Ok(s)
}
// 最简洁的版本
fn read_username_from_file_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("username.txt")
}错误传播的链式调用
rust
fn error_propagation_chain() -> Result<i32, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("numbers.txt")?;
let number: i32 = content.trim().parse()?;
let result = divide_by_two(number)?;
Ok(result)
}
fn divide_by_two(n: i32) -> Result<i32, String> {
if n % 2 != 0 {
Err("数字必须是偶数".to_string())
} else {
Ok(n / 2)
}
}
fn demonstration_error_propagation() {
match error_propagation_chain() {
Ok(result) => println!("最终结果: {}", result),
Err(error) => println!("错误: {}", error),
}
}🛠️ 自定义错误类型
简单的自定义错误
rust
#[derive(Debug)]
enum CalculatorError {
DivisionByZero,
InvalidInput,
Overflow,
}
impl std::fmt::Display for CalculatorError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CalculatorError::DivisionByZero => write!(f, "不能除以零"),
CalculatorError::InvalidInput => write!(f, "输入无效"),
CalculatorError::Overflow => write!(f, "数值溢出"),
}
}
}
impl std::error::Error for CalculatorError {}
fn safe_divide(dividend: i32, divisor: i32) -> Result<i32, CalculatorError> {
if divisor == 0 {
return Err(CalculatorError::DivisionByZero);
}
match dividend.checked_div(divisor) {
Some(result) => Ok(result),
None => Err(CalculatorError::Overflow),
}
}
fn custom_error_example() {
let operations = vec![
(10, 2),
(10, 0),
(i32::MIN, -1),
];
for (a, b) in operations {
match safe_divide(a, b) {
Ok(result) => println!("{} / {} = {}", a, b, result),
Err(error) => println!("{} / {} 失败: {}", a, b, error),
}
}
}复杂的错误类型
rust
use std::fmt;
#[derive(Debug)]
struct ParseError {
input: String,
kind: ParseErrorKind,
}
#[derive(Debug)]
enum ParseErrorKind {
EmptyInput,
InvalidCharacter(char),
NumberTooLarge,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "解析 '{}' 时出错: ", self.input)?;
match &self.kind {
ParseErrorKind::EmptyInput => write!(f, "输入为空"),
ParseErrorKind::InvalidCharacter(c) => write!(f, "无效字符 '{}'", c),
ParseErrorKind::NumberTooLarge => write!(f, "数字过大"),
}
}
}
impl std::error::Error for ParseError {}
fn parse_positive_number(input: &str) -> Result<u32, ParseError> {
if input.is_empty() {
return Err(ParseError {
input: input.to_string(),
kind: ParseErrorKind::EmptyInput,
});
}
for ch in input.chars() {
if !ch.is_ascii_digit() {
return Err(ParseError {
input: input.to_string(),
kind: ParseErrorKind::InvalidCharacter(ch),
});
}
}
match input.parse::<u32>() {
Ok(number) => Ok(number),
Err(_) => Err(ParseError {
input: input.to_string(),
kind: ParseErrorKind::NumberTooLarge,
}),
}
}
fn complex_error_example() {
let test_inputs = vec!["123", "", "12a", "999999999999999999999"];
for input in test_inputs {
match parse_positive_number(input) {
Ok(number) => println!("解析成功: {} -> {}", input, number),
Err(error) => println!("解析失败: {}", error),
}
}
}错误转换和兼容性
rust
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(ParseIntError),
Custom(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(err) => write!(f, "IO 错误: {}", err),
AppError::Parse(err) => write!(f, "解析错误: {}", err),
AppError::Custom(msg) => write!(f, "自定义错误: {}", msg),
}
}
}
impl std::error::Error for AppError {}
// 自动转换
impl From<std::io::Error> for AppError {
fn from(error: std::io::Error) -> Self {
AppError::Io(error)
}
}
impl From<ParseIntError> for AppError {
fn from(error: ParseIntError) -> Self {
AppError::Parse(error)
}
}
fn process_file(filename: &str) -> Result<i32, AppError> {
let content = std::fs::read_to_string(filename)?; // 自动转换 io::Error
let number: i32 = content.trim().parse()?; // 自动转换 ParseIntError
if number < 0 {
return Err(AppError::Custom("数字不能为负".to_string()));
}
Ok(number * 2)
}
fn error_conversion_example() {
match process_file("number.txt") {
Ok(result) => println!("处理结果: {}", result),
Err(error) => println!("处理失败: {}", error),
}
}🎭 高级错误处理模式
多种错误类型的处理
rust
use std::collections::HashMap;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
struct UserManager {
users: HashMap<u32, User>,
}
impl UserManager {
fn new() -> Self {
Self {
users: HashMap::new(),
}
}
fn add_user(&mut self, id: u32, name: String) -> Result<()> {
if self.users.contains_key(&id) {
return Err("用户ID已存在".into());
}
if name.is_empty() {
return Err("用户名不能为空".into());
}
self.users.insert(id, User { id, name });
Ok(())
}
fn get_user(&self, id: u32) -> Result<&User> {
self.users.get(&id).ok_or_else(|| "用户不存在".into())
}
fn save_to_file(&self, filename: &str) -> Result<()> {
let json = serde_json::to_string(&self.users)?;
std::fs::write(filename, json)?;
Ok(())
}
fn load_from_file(&mut self, filename: &str) -> Result<()> {
let content = std::fs::read_to_string(filename)?;
self.users = serde_json::from_str(&content)?;
Ok(())
}
}
fn advanced_error_handling() {
let mut manager = UserManager::new();
// 链式错误处理
let result = manager
.add_user(1, "Alice".to_string())
.and_then(|_| manager.add_user(2, "Bob".to_string()))
.and_then(|_| manager.save_to_file("users.json"));
match result {
Ok(_) => println!("操作成功"),
Err(error) => println!("操作失败: {}", error),
}
}恢复策略
rust
fn retry_with_backoff<F, T, E>(mut operation: F, max_retries: u32) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
E: std::fmt::Debug,
{
let mut delay = std::time::Duration::from_millis(100);
for attempt in 0..=max_retries {
match operation() {
Ok(result) => return Ok(result),
Err(error) => {
if attempt == max_retries {
return Err(error);
}
println!("尝试 {} 失败: {:?}, {}ms 后重试", attempt + 1, error, delay.as_millis());
std::thread::sleep(delay);
delay *= 2; // 指数退避
}
}
}
unreachable!()
}
fn unreliable_operation() -> Result<String, String> {
use rand::Rng;
let mut rng = rand::thread_rng();
if rng.gen_bool(0.7) { // 70% 失败率
Err("网络连接失败".to_string())
} else {
Ok("操作成功".to_string())
}
}
fn recovery_strategy_example() {
match retry_with_backoff(unreliable_operation, 3) {
Ok(result) => println!("最终成功: {}", result),
Err(error) => println!("最终失败: {}", error),
}
}📝 本章小结
通过本章学习,你应该掌握了:
基础错误处理
- ✅ Option 和 Result 类型的使用
- ✅ 各种错误处理方法
- ✅ ? 运算符的错误传播
- ✅ match 和 if let 的选择
高级错误处理
- ✅ 自定义错误类型的设计
- ✅ 错误转换和兼容性
- ✅ 多种错误类型的统一处理
- ✅ 错误恢复策略
最佳实践
- 优先使用 Result 而非 panic
- 设计清晰的错误类型层次
- 提供有意义的错误信息
- 合理使用 ? 运算符
- 考虑错误的可恢复性
继续学习:下一章 - Rust 泛型与特性