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程序正常结束");
}
相关推荐
章豪Mrrey nical2 小时前
前后端分离工作详解Detailed Explanation of Frontend-Backend Separation Work
后端·前端框架·状态模式
派大鑫wink3 小时前
【JAVA学习日志】SpringBoot 参数配置:从基础到实战,解锁灵活配置新姿势
java·spring boot·后端
程序员爱钓鱼3 小时前
Node.js 编程实战:文件读写操作
前端·后端·node.js
xUxIAOrUIII3 小时前
【Spring Boot】控制器Controller方法
java·spring boot·后端
Dolphin_Home3 小时前
从理论到实战:图结构在仓库关联业务中的落地(小白→中级,附完整代码)
java·spring boot·后端·spring cloud·database·广度优先·图搜索算法
zfj3213 小时前
go为什么设计成源码依赖,而不是二进制依赖
开发语言·后端·golang
weixin_462446234 小时前
使用 Go 实现 SSE 流式推送 + 打字机效果(模拟 Coze Chat)
开发语言·后端·golang
JIngJaneIL4 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小信啊啊4 小时前
Go语言切片slice
开发语言·后端·golang
Victor3566 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端