Skip to content

Rust 错误处理

概述

Rust 采用了独特的错误处理方式,通过类型系统来处理错误,而不是使用异常机制。本章将学习 Rust 中的错误处理模式,包括 ResultOption 类型,以及错误传播和自定义错误类型。

🎯 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 的选择

高级错误处理

  • ✅ 自定义错误类型的设计
  • ✅ 错误转换和兼容性
  • ✅ 多种错误类型的统一处理
  • ✅ 错误恢复策略

最佳实践

  1. 优先使用 Result 而非 panic
  2. 设计清晰的错误类型层次
  3. 提供有意义的错误信息
  4. 合理使用 ? 运算符
  5. 考虑错误的可恢复性

继续学习下一章 - Rust 泛型与特性

本站内容仅供学习和研究使用。