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. 用户友好:向用户显示友好的错误信息,而不是崩溃堆栈

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

相关推荐
DongLi012 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
番茄灭世神3 天前
Rust学习笔记第2篇
rust·编程语言
shimly1234563 天前
(done) 速通 rustlings(20) 错误处理1 --- 不涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(19) Option
rust
@atweiwei3 天前
rust所有权机制详解
开发语言·数据结构·后端·rust·内存·所有权
shimly1234563 天前
(done) 速通 rustlings(24) 错误处理2 --- 涉及Traits
rust
shimly1234563 天前
(done) 速通 rustlings(23) 特性 Traits
rust
shimly1234563 天前
(done) 速通 rustlings(17) 哈希表
rust
shimly1234563 天前
(done) 速通 rustlings(15) 字符串
rust
shimly1234563 天前
(done) 速通 rustlings(22) 泛型
rust