Rust错误处理

Rust 的异常处理与其他语言有很大不同,主要通过两种机制:panic!Result<T, E>

1. 不可恢复错误:Panic

panic! 宏

fn main() {
    // 程序会立即终止,展开堆栈
    panic!("crash and burn");
    
    // 也可以这样使用
    if true {
        panic!("Something went wrong!");
    }
}

设置 panic 行为

// 在 Cargo.toml 中设置
[profile.release]
panic = 'abort'  // 直接终止,不清理内存(更小的二进制文件)

[profile.dev]
panic = 'unwind'  // 默认:展开堆栈,调用析构函数

2. 可恢复错误:Result 类型

Result 枚举

enum Result<T, E> {
    Ok(T),   // 成功
    Err(E),  // 错误
}

基本用法

use std::fs::File;
use std::io::Error;

fn read_file() -> Result<String, std::io::Error> {
    let mut file = File::open("hello.txt")?;  // ? 运算符传播错误
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

3. 错误处理方式

使用 match

let result = File::open("hello.txt");

match result {
    Ok(file) => {
        println!("File opened successfully: {:?}", file);
    },
    Err(error) => {
        match error.kind() {
            std::io::ErrorKind::NotFound => {
                println!("File not found");
            },
            other_error => {
                println!("Error opening file: {:?}", other_error);
            }
        }
    }
}

便捷方法

// unwrap - 成功返回值,失败则 panic
let file = File::open("hello.txt").unwrap();

// expect - 类似 unwrap,可自定义错误信息
let file = File::open("hello.txt")
    .expect("Failed to open hello.txt");

// unwrap_or - 提供默认值
let value: i32 = "not_a_number".parse().unwrap_or(0);

// unwrap_or_else - 通过闭包提供默认值
let file = File::open("hello.txt").unwrap_or_else(|error| {
    if error.kind() == ErrorKind::NotFound {
        File::create("hello.txt").unwrap()
    } else {
        panic!("Problem opening the file: {:?}", error);
    }
});

4. ? 运算符

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    // ? 运算符会自动将错误转换为返回类型
    let mut s = String::new();
    fs::File::open("hello.txt")?
        .read_to_string(&mut s)?;
    Ok(s)
}

// 更简洁的写法
fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

5. 自定义错误类型

使用 thiserror 库(推荐)

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("Parse error: {0}")]
    Parse(#[from] std::num::ParseIntError),
    
    #[error("Custom error: {message}")]
    Custom { message: String },
}

fn process() -> Result<(), MyError> {
    let num = "abc".parse::<i32>()?;  // 自动转换为 MyError
    Ok(())
}

手动实现

use std::fmt;
use std::error::Error;

#[derive(Debug)]
struct MyError {
    details: String,
}

impl MyError {
    fn new(msg: &str) -> MyError {
        MyError { details: msg.to_string() }
    }
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.details)
    }
}

impl Error for MyError {
    fn description(&self) -> &str {
        &self.details
    }
}

6. 错误转换

// 使用 map_err
fn string_length(path: &str) -> Result<usize, String> {
    std::fs::read_to_string(path)
        .map(|s| s.len())
        .map_err(|e| format!("Failed to read file: {}", e))
}

// 使用 From trait 自动转换
impl From<std::io::Error> for MyError {
    fn from(error: std::io::Error) -> Self {
        MyError::new(&format!("IO error: {}", error))
    }
}

7. 错误传播模式

// 链式调用
fn process_data() -> Result<Data, MyError> {
    let input = read_input()?
        .validate()?
        .transform()?
        .finalize()?;
    Ok(input)
}

// 组合多个结果
fn process_multiple() -> Result<(), MyError> {
    let result1 = fallible_op1();
    let result2 = fallible_op2();
    
    // 使用 and_then 链式调用
    result1.and_then(|r1| {
        result2.map(|r2| r1 + r2)
    })?;
    
    Ok(())
}

8. 最佳实践

  1. 库代码

    • 返回 Result<T, E> 而不是 panic

    • 定义明确的错误类型

    • 使用 thiserroranyhow

  2. 应用程序

    • 在 main 函数中处理错误

    • 使用 anyhow 简化错误处理

    • 只在不可恢复的情况下使用 panic

  3. 测试

    #[test]
    #[should_panic(expected = "out of bounds")]
    fn test_panic() {
        let v = vec![1, 2, 3];
        v[99];
    }
    

9. 实用库推荐

  • thiserror:用于定义自定义错误类型

  • anyhow:用于应用程序的错误处理

  • color-eyre:带彩色输出的错误报告

  • miette:漂亮的诊断错误

Rust 的错误处理强调显式和编译时检查,这使得错误处理更加可靠和安全。通过类型系统确保所有可能的错误路径都被考虑,避免了意外的未处理异常。

Rust 提供了多种策略来处理未预期的错误,避免程序崩溃。

1. 顶层错误处理

在 main 函数中处理

fn main() {
    if let Err(e) = run_application() {
        eprintln!("Application error: {}", e);
        
        // 记录错误日志
        log_error(&e);
        
        // 优雅关闭资源
        cleanup_resources();
        
        // 非零退出码
        std::process::exit(1);
    }
}

fn run_application() -> Result<(), Box<dyn std::error::Error>> {
    // 所有应用逻辑
    your_logic_here()?;
    Ok(())
}

2. 使用 anyhow 库简化错误处理

use anyhow::{Context, Result, bail};
use anyhow::anyhow;

fn process() -> Result<()> {
    // 自动包装所有错误类型
    let data = read_config()
        .context("Failed to read config")?;
    
    process_data(&data)
        .with_context(|| format!("Processing failed for {:?}", data))?;
    
    Ok(())
}

// 在 main 中使用
fn main() -> Result<()> {
    // 自动格式化漂亮的错误链
    process()?;
    Ok(())
}

3. 设置全局 panic 钩子

use std::panic;
use std::process;

// 在 main 函数开始时设置
fn main() {
    // 设置自定义 panic 处理器
    panic::set_hook(Box::new(|panic_info| {
        eprintln!("===== UNEXPECTED PANIC =====");
        eprintln!("Message: {:?}", panic_info.payload().downcast_ref::<String>());
        eprintln!("Location: {:?}", panic_info.location());
        
        // 记录堆栈跟踪
        #[cfg(debug_assertions)]
        {
            eprintln!("Backtrace:");
            eprintln!("{:?}", backtrace::Backtrace::new());
        }
        
        // 尝试保存用户数据
        emergency_save();
        
        eprintln!("Application will now exit");
    }));
    
    // 运行主逻辑
    run_app();
}

// 或者使用更强大的 human-panic
fn main() {
    human_panic::setup_panic!();
    // 你的代码
}

4. 隔离可能 panic 的代码

使用 catch_unwind

use std::panic;

fn safe_call<F, R>(f: F) -> Result<R, String>
where
    F: FnOnce() -> R + panic::UnwindSafe,
{
    match panic::catch_unwind(f) {
        Ok(result) => Ok(result),
        Err(_) => {
            // 处理 panic
            log::error!("Function panicked, recovering...");
            Err("Function panicked".to_string())
        }
    }
}

// 使用示例
fn process_untrusted_input(input: &str) -> Result<String, String> {
    safe_call(|| {
        // 可能 panic 的代码
        if input.is_empty() {
            panic!("Empty input!");
        }
        format!("Processed: {}", input)
    })
}

5. 线程级别的隔离

use std::thread;
use std::time::Duration;

fn main() {
    // 启动监控线程
    let monitor = thread::spawn(|| {
        loop {
            // 检查各线程状态
            thread::sleep(Duration::from_secs(5));
        }
    });
    
    // 工作线程,单个线程崩溃不会影响整个程序
    let workers: Vec<_> = (0..4)
        .map(|id| {
            thread::spawn(move || {
                if let Err(e) = work(id) {
                    eprintln!("Worker {} failed: {}", id, e);
                    // 线程退出,但进程继续
                }
            })
        })
        .collect();
    
    // 等待工作线程完成
    for worker in workers {
        let _ = worker.join();
    }
    
    // 关闭监控
    monitor.join().unwrap();
}

6. 监控和自动恢复

struct ResilientService {
    max_restarts: usize,
    restart_delay: Duration,
}

impl ResilientService {
    fn run_with_recovery<F>(&self, mut task: F) 
    where
        F: FnMut() -> Result<(), String> + Send + 'static,
    {
        let mut restart_count = 0;
        
        while restart_count < self.max_restarts {
            match task() {
                Ok(_) => break,  // 成功完成
                Err(e) => {
                    restart_count += 1;
                    eprintln!("Restart #{}, error: {}", restart_count, e);
                    
                    if restart_count >= self.max_restarts {
                        eprintln!("Max restarts reached, giving up");
                        break;
                    }
                    
                    thread::sleep(self.restart_delay);
                }
            }
        }
    }
}

7. 配置驱动的错误处理策略

enum ErrorHandlingStrategy {
    Ignore,          // 忽略错误继续执行
    Log,             // 记录日志并继续
    Retry(usize),    // 重试指定次数
    Fallback,        // 使用备用逻辑
    Halt,            // 停止当前操作
    Exit,            // 退出程序
}

struct ApplicationConfig {
    panic_strategy: PanicStrategy,
    error_strategy: HashMap<String, ErrorHandlingStrategy>,
    max_restarts: usize,
}

impl Application {
    fn handle_error(&self, error: &dyn Error, context: &str) {
        let strategy = self.config.error_strategy
            .get(context)
            .unwrap_or(&ErrorHandlingStrategy::Log);
        
        match strategy {
            ErrorHandlingStrategy::Ignore => {}
            ErrorHandlingStrategy::Log => {
                eprintln!("[{}] Error: {}", context, error);
            }
            ErrorHandlingStrategy::Retry(times) => {
                self.retry_operation(context, *times);
            }
            ErrorHandlingStrategy::Fallback => {
                self.use_fallback(context);
            }
            ErrorHandlingStrategy::Halt => {
                self.halt_operation(context);
            }
            ErrorHandlingStrategy::Exit => {
                std::process::exit(1);
            }
        }
    }
}

8. 优雅降级

enum ServiceQuality {
    Full,       // 完整功能
    Degraded,   // 降级运行
    Minimal,    // 最小功能
    Offline,    // 仅本地功能
}

impl Application {
    fn determine_service_quality() -> ServiceQuality {
        // 检查依赖服务
        if !self.check_database() {
            return ServiceQuality::Degraded;
        }
        
        if !self.check_cache() {
            return ServiceQuality::Minimal;
        }
        
        if !self.check_network() {
            return ServiceQuality::Offline;
        }
        
        ServiceQuality::Full
    }
    
    fn run_with_graceful_degradation(&self) {
        match self.determine_service_quality() {
            ServiceQuality::Full => self.run_full_features(),
            ServiceQuality::Degraded => {
                log::warn!("Running in degraded mode");
                self.run_basic_features();
            }
            ServiceQuality::Minimal => {
                log::error!("Running in minimal mode");
                self.run_essential_only();
            }
            ServiceQuality::Offline => {
                log::error!("Running offline");
                self.run_local_only();
            }
        }
    }
}

9. 健康检查和看门狗

struct Watchdog {
    health_check_interval: Duration,
    unhealthy_threshold: usize,
}

impl Watchdog {
    fn monitor<F>(&self, mut health_check: F)
    where
        F: FnMut() -> bool + Send + 'static,
    {
        let mut unhealthy_count = 0;
        
        thread::spawn(move || {
            loop {
                thread::sleep(self.health_check_interval);
                
                if health_check() {
                    unhealthy_count = 0;  // 健康,重置计数
                } else {
                    unhealthy_count += 1;
                    eprintln!("Health check failed {} times", unhealthy_count);
                    
                    if unhealthy_count >= self.unhealthy_threshold {
                        eprintln!("Watchdog: restarting service...");
                        self.restart_service();
                        unhealthy_count = 0;
                    }
                }
            }
        });
    }
}

10. 错误边界模式

trait ErrorBoundary {
    type Output;
    
    fn execute(&self) -> Result<Self::Output, String>;
    fn on_error(&self, error: String) -> Self::Output;
    fn finally(&self) {}
}

impl<F, T> ErrorBoundary for F
where
    F: Fn() -> Result<T, String>,
    T: Default,
{
    type Output = T;
    
    fn execute(&self) -> Result<T, String> {
        (self)()
    }
    
    fn on_error(&self, error: String) -> T {
        eprintln!("Error boundary caught: {}", error);
        T::default()  // 返回安全默认值
    }
}

// 使用
fn process_with_boundary() {
    let boundary = || -> Result<String, String> {
        dangerous_operation()?;
        Ok("Success".to_string())
    };
    
    let result = if let Ok(value) = boundary.execute() {
        value
    } else {
        boundary.on_error("Operation failed".to_string())
    };
    
    boundary.finally();
}

最佳实践总结

  1. 顶层捕获:在 main 函数或最外层使用 catch_unwind 或 panic 钩子

  2. 隔离失败:将可能失败的部分隔离在单独线程或进程中

  3. 监控重启:对关键服务实现监控和自动重启

  4. 优雅降级:准备好降级方案,在部分功能失效时仍能提供服务

  5. 资源清理:确保 panic 时能正确释放资源

  6. 记录诊断:记录足够的诊断信息以便调试

  7. 用户友好:向用户显示友好的错误信息,而不是崩溃堆栈

通过这些策略,即使遇到未预期的错误,程序也能优雅地处理,避免崩溃,保持最大可能的可用性。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值