Rust基础之异常

Rust通过 Result 枚举panic!来处理错误和异常情况。这种设计强调显式错误处理,避免了未捕获异常导致的程序意外终止。

1. Rust 错误处理的核心概念

Rust 将错误分为两类:

  • 可恢复错误 :使用 Result<T, E> 枚举表示,程序可以从中恢复并继续运行

  • 不可恢复错误 :使用 panic! 宏处理,会导致程序终止

Result 枚举定义如下:

rust

scss 复制代码
enum Result<T, E> {
    Ok(T),  // 操作成功,包含结果值
    Err(E), // 操作失败,包含错误信息
}

2. 可恢复错误处理详解

2.1 ? 运算符
  • 用于传播错误,当 ResultErr 时,自动返回该错误
  • 相当于简化的 match 表达式:if let Err(e) = result { return Err(e); }
  • 只能用于返回 ResultOption 的函数中
2.2 自定义错误类型
  • 通常使用枚举定义,可包含多种错误情况
  • 实现 DisplayError trait 使其成为标准错误类型
  • 可以包装其他错误类型(如示例中的 FileReadError(io::Error)
2.3 错误处理方法
  • 完整匹配 :使用 match 表达式处理 OkErr 两种情况

  • 简化处理 :使用 if let Err(e) = result 只处理错误情况

  • 快速处理

    • unwrap():成功时返回值,失败时 panic!
    • expect(msg):类似 unwrap(),但可自定义错误消息
    • unwrap_or(default):失败时返回默认值
    • unwrap_or_else(f):失败时通过函数计算返回值

3. 不可恢复错误(panic!

panic! 宏用于处理程序无法恢复的错误:

  • 触发时会打印错误消息、堆栈跟踪,并终止程序
  • 适合处理逻辑错误(如违反不变式、无效状态)
  • 生产环境中应尽量避免,除非确实无法恢复

4. 错误处理最佳实践

  1. 优先使用 Result :对于可能失败的操作(如文件 IO、网络请求),返回 Result
  2. 合理使用 panic! :仅在程序处于无法恢复的错误状态时使用
  3. 定义自定义错误类型:使错误信息更具体,便于调用者处理
  4. 避免过度使用 unwrap() :生产代码中应显式处理错误
  5. 错误链 :通过 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程序正常结束");
}
相关推荐
毕设源码-赖学姐9 小时前
【开题答辩全过程】以 基于Spring Boot的网上家庭烹饪学习系统的设计与实现为例,包含答辩的问题和答案
spring boot·后端·学习
Moonbit9 小时前
MoonBit Pearls Vol.08: MoonBit 与 Python集成指南
后端·python·程序员
刘祯昊9 小时前
中望CAD二次开发(一)——开发环境配置
后端·c#
唐天一9 小时前
Rust语法之模块系统
后端
唐天一9 小时前
Rust面向对象:简单总结impl ... for ... 结构在 Rust 中两种主要用途
后端
唐天一9 小时前
Rust语法之面向对象编程
后端
brzhang9 小时前
Google 浏览器出了一个超级好用的功能,Gemini 原生支持,帮你解决性能问题
前端·后端·架构
二闹9 小时前
三招搞定Python定时任务,总有一款适合你
后端·python
京东云开发者9 小时前
KCP协议应用详解:为速度而生的可靠传输协议
后端