Skip to content

Rust 错误处理完全指南

📚 一、Rust 错误处理哲学

Rust 将错误分为两类:

  1. 可恢复错误(Recoverable):使用 Result<T, E> 类型
  2. 不可恢复错误(Unrecoverable):使用 panic!

🎯 二、基础错误处理

1. panic! 宏 - 不可恢复错误

fn panic_example() {
    // 1. 直接调用 panic!
    // panic!("程序崩溃了!");

    // 2. 断言失败时 panic
    let x = 10;
    assert!(x > 0, "x 必须大于 0");

    // 3. 断言相等
    assert_eq!(x, 10, "x 应该等于 10");

    // 4. 不可达代码
    if x > 100 {
        unreachable!("x 不应该大于 100");
    }

    // 5. 调试断言(只在 debug 构建中检查)
    debug_assert!(x > 0);
}

2. Result<T, E> 类型 - 可恢复错误

use std::fs::File;
use std::io::{Read, Error};

fn basic_result() -> Result<String, Error> {
    // 打开文件可能失败
    let mut file = File::open("hello.txt")?;

    let mut content = String::new();
    // 读取文件可能失败
    file.read_to_string(&mut content)?;

    Ok(content)
}

fn handle_result() {
    match basic_result() {
        Ok(content) => println!("文件内容: {}", content),
        Err(e) => println!("读取文件失败: {}", e),
    }

    // 使用 if let
    if let Ok(content) = basic_result() {
        println!("内容: {}", content);
    }

    // 使用 unwrap 或 expect
    let content = basic_result().unwrap();  // 失败时 panic
    let content = basic_result().expect("读取文件失败");  // 失败时 panic 并显示信息
}

🔧 三、Result 的实用方法

1. 解包方法

fn unwrap_methods() {
    let ok_result: Result<i32, &str> = Ok(42);
    let err_result: Result<i32, &str> = Err("出错了");

    // 1. unwrap - 成功返回值,失败 panic
    let value1 = ok_result.unwrap();  // 42
    // let value2 = err_result.unwrap();  // ❌ panic!

    // 2. expect - 同 unwrap,但可自定义错误信息
    let value3 = ok_result.expect("不应该出错");  // 42
    // let value4 = err_result.expect("自定义错误信息");  // ❌ panic!

    // 3. unwrap_or - 成功返回值,失败返回默认值
    let value5 = ok_result.unwrap_or(0);  // 42
    let value6 = err_result.unwrap_or(0);  // 0

    // 4. unwrap_or_else - 成功返回值,失败执行闭包
    let value7 = err_result.unwrap_or_else(|err| {
        println!("错误: {}", err);
        0
    });  // 0

    // 5. unwrap_or_default - 成功返回值,失败返回默认值
    let ok_string: Result<String, &str> = Ok("hello".to_string());
    let err_string: Result<String, &str> = Err("错误");

    let s1 = ok_string.unwrap_or_default();  // "hello"
    let s2 = err_string.unwrap_or_default();  // "" (空字符串)
}

2. 查询方法

fn query_methods() {
    let ok_result: Result<i32, &str> = Ok(42);
    let err_result: Result<i32, &str> = Err("出错了");

    // 1. is_ok / is_err - 检查结果
    println!("ok_result.is_ok(): {}", ok_result.is_ok());    // true
    println!("ok_result.is_err(): {}", ok_result.is_err());  // false

    // 2. as_ref / as_mut - 转换为引用
    let ok_ref: Result<&i32, &str> = ok_result.as_ref();
    let ok_mut: Result<&mut i32, &mut str> = ok_result.as_mut();

    // 3. ok - 转换为 Option
    let option1: Option<i32> = ok_result.ok();  // Some(42)
    let option2: Option<i32> = err_result.ok(); // None

    // 4. err - 获取错误
    let error1: Option<&str> = ok_result.err();  // None
    let error2: Option<&str> = err_result.err(); // Some("出错了")
}

3. 转换方法

fn transform_methods() {
    let result: Result<i32, &str> = Ok(42);

    // 1. map - 转换成功值
    let doubled: Result<i32, &str> = result.map(|x| x * 2);  // Ok(84)

    // 2. map_err - 转换错误值
    let new_error: Result<i32, String> = result.map_err(|e| e.to_string());

    // 3. and_then - 链式调用
    let chained: Result<String, &str> = result.and_then(|x| {
        if x > 0 {
            Ok(x.to_string())
        } else {
            Err("值太小")
        }
    });

    // 4. or_else - 处理错误
    let recovered: Result<i32, &str> = Err("错误").or_else(|_| Ok(100));

    // 5. and / or - 组合 Result
    let ok1: Result<i32, &str> = Ok(1);
    let ok2: Result<i32, &str> = Ok(2);
    let err: Result<i32, &str> = Err("错误");

    let and_result = ok1.and(ok2);  // Ok(2)
    let or_result = err.or(ok1);    // Ok(1)
}

🎨 四、自定义错误类型

1. 使用枚举定义错误

use std::fmt;
use std::io;

#[derive(Debug)]
enum AppError {
    Io(io::Error),              // IO 错误
    Parse(std::num::ParseIntError),  // 解析错误
    Custom(String),             // 自定义错误
    Overflow,                   // 溢出错误
    NotFound,                   // 未找到错误
}

// 实现 Display trait
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO错误: {}", e),
            AppError::Parse(e) => write!(f, "解析错误: {}", e),
            AppError::Custom(msg) => write!(f, "自定义错误: {}", msg),
            AppError::Overflow => write!(f, "数值溢出错误"),
            AppError::NotFound => write!(f, "未找到资源"),
        }
    }
}

// 实现 Error trait
impl std::error::Error for AppError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            AppError::Io(e) => Some(e),
            AppError::Parse(e) => Some(e),
            _ => None,
        }
    }
}

// 实现 From trait 用于自动转换
impl From<io::Error> for AppError {
    fn from(e: io::Error) -> Self {
        AppError::Io(e)
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self {
        AppError::Parse(e)
    }
}

// 使用示例
fn process_file(filename: &str) -> Result<i32, AppError> {
    use std::fs;

    let content = fs::read_to_string(filename)?;  // 自动转换为 AppError::Io

    let number: i32 = content.trim().parse()?;  // 自动转换为 AppError::Parse

    if number > 1000 {
        return Err(AppError::Overflow);
    }

    Ok(number)
}

2. 使用 thiserror 库简化

[dependencies]
thiserror = "1.0"
use thiserror::Error;
use std::io;

#[derive(Error, Debug)]
enum AppError {
    #[error("IO错误: {0}")]
    Io(#[from] io::Error),

    #[error("解析错误: {0}")]
    Parse(#[from] std::num::ParseIntError),

    #[error("自定义错误: {0}")]
    Custom(String),

    #[error("数值溢出错误")]
    Overflow,

    #[error("未找到资源: {0}")]
    NotFound(String),

    #[error("配置错误: {0}")]
    Config(#[from] ConfigError),
}

#[derive(Error, Debug)]
enum ConfigError {
    #[error("缺少配置项: {0}")]
    Missing(String),

    #[error("无效配置: {0}")]
    Invalid(String),
}

fn load_config() -> Result<(), ConfigError> {
    Err(ConfigError::Missing("api_key".to_string()))
}

fn process() -> Result<(), AppError> {
    load_config()?;  // 自动转换为 AppError::Config
    Ok(())
}

3. 使用 anyhow 库简化应用程序错误

[dependencies]
anyhow = "1.0"
use anyhow::{Context, Result, bail, ensure};
use std::fs;

fn process_file(path: &str) -> Result<String> {
    // 使用 context 添加错误上下文
    let content = fs::read_to_string(path)
        .context(format!("读取文件失败: {}", path))?;

    // 使用 ensure 进行条件检查
    ensure!(!content.is_empty(), "文件内容为空");

    // 使用 bail 提前返回错误
    if content.len() > 1000 {
        bail!("文件太大: {} 字节", content.len());
    }

    Ok(content)
}

fn complex_operation() -> Result<()> {
    let data1 = process_file("file1.txt")?;
    let data2 = process_file("file2.txt")?;

    // 使用 with_context 链式添加上下文
    let result = process_file("file3.txt")
        .with_context(|| format!("处理组合数据失败: {}, {}", data1, data2))?;

    Ok(())
}

🔄 五、错误传播模式

1. ? 运算符

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

// 传统写法
fn read_file_old(path: &str) -> Result<String, io::Error> {
    let file = match fs::File::open(path) {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    // ... 更多处理
    Ok(String::new())
}

// 使用 ? 运算符
fn read_file_new(path: &str) -> Result<String, io::Error> {
    let content = fs::read_to_string(path)?;
    Ok(content)
}

// 链式调用
fn process_data() -> Result<(), Box<dyn std::error::Error>> {
    let data1 = fs::read_to_string("file1.txt")?;
    let data2 = fs::read_to_string("file2.txt")?;
    let data3 = fs::read_to_string("file3.txt")?;

    // 处理数据...
    Ok(())
}

2. 错误链和上下文

use anyhow::{Context, Result};
use std::fs;
use std::path::Path;

fn load_config_file() -> Result<String> {
    let paths = [
        "./config.toml",
        "~/.config/app/config.toml",
        "/etc/app/config.toml",
    ];

    for path in &paths {
        if Path::new(path).exists() {
            return fs::read_to_string(path)
                .with_context(|| format!("无法读取配置文件: {}", path));
        }
    }

    anyhow::bail!("未找到配置文件");
}

fn parse_config() -> Result<Config> {
    let content = load_config_file()?;

    // 解析配置
    let config: Config = toml::from_str(&content)
        .context("解析配置文件失败")?;

    Ok(config)
}

struct Config;

🏗️ 六、错误处理模式

1. 错误包装模式

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

#[derive(Debug)]
struct OperationError {
    operation: String,
    source: Box<dyn Error>,
}

impl fmt::Display for OperationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "操作 '{}' 失败: {}", self.operation, self.source)
    }
}

impl Error for OperationError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(self.source.as_ref())
    }
}

// 包装器函数
fn wrap_error<T, E>(result: Result<T, E>, operation: &str) -> Result<T, OperationError>
where
    E: Error + 'static,
{
    result.map_err(|e| OperationError {
        operation: operation.to_string(),
        source: Box::new(e),
    })
}

// 使用示例
fn process() -> Result<(), Box<dyn Error>> {
    let data = wrap_error(std::fs::read_to_string("data.txt"), "读取文件")?;
    let parsed = wrap_error(data.parse::<i32>(), "解析数字")?;
    Ok(())
}

2. 错误类型转换

use std::num::ParseIntError;

fn parse_and_multiply(a: &str, b: &str) -> Result<i32, String> {
    let x = a.parse::<i32>()
        .map_err(|e: ParseIntError| format!("解析 {} 失败: {}", a, e))?;

    let y = b.parse::<i32>()
        .map_err(|e| format!("解析 {} 失败: {}", b, e))?;

    x.checked_mul(y)
        .ok_or_else(|| "乘法溢出".to_string())
}

// 使用 map_err 和 or_else
fn process_data(data: &str) -> Result<i32, String> {
    data.parse::<i32>()
        .map_err(|e| e.to_string())
        .and_then(|n| {
            if n > 0 {
                Ok(n)
            } else {
                Err("数字必须为正数".to_string())
            }
        })
}

📁 七、文件和 I/O 错误处理

use std::fs;
use std::io;
use std::path::Path;

#[derive(Debug)]
enum FileError {
    NotFound(String),
    PermissionDenied(String),
    Io(io::Error),
    InvalidFormat(String),
}

impl std::error::Error for FileError {}
impl std::fmt::Display for FileError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            FileError::NotFound(path) => write!(f, "文件未找到: {}", path),
            FileError::PermissionDenied(path) => write!(f, "权限不足: {}", path),
            FileError::Io(e) => write!(f, "IO错误: {}", e),
            FileError::InvalidFormat(msg) => write!(f, "格式错误: {}", msg),
        }
    }
}

impl From<io::Error> for FileError {
    fn from(e: io::Error) -> Self {
        match e.kind() {
            io::ErrorKind::NotFound => FileError::NotFound(e.to_string()),
            io::ErrorKind::PermissionDenied => FileError::PermissionDenied(e.to_string()),
            _ => FileError::Io(e),
        }
    }
}

fn process_file_safely(path: &str) -> Result<String, FileError> {
    // 检查文件是否存在
    if !Path::new(path).exists() {
        return Err(FileError::NotFound(path.to_string()));
    }

    // 读取文件
    let content = fs::read_to_string(path)?;

    // 验证内容
    if content.trim().is_empty() {
        return Err(FileError::InvalidFormat("文件为空".to_string()));
    }

    Ok(content)
}

// 批量处理文件
fn process_multiple_files(paths: &[&str]) -> Vec<Result<String, FileError>> {
    paths.iter()
        .map(|&path| process_file_safely(path))
        .collect()
}

🔧 八、网络和 HTTP 错误处理

use reqwest;
use serde_json;
use thiserror::Error;
use std::time::Duration;

#[derive(Error, Debug)]
enum ApiError {
    #[error("网络请求失败: {0}")]
    Network(#[from] reqwest::Error),

    #[error("解析JSON失败: {0}")]
    Parse(#[from] serde_json::Error),

    #[error("API错误: {0} - {1}")]
    Http(u16, String),

    #[error("超时")]
    Timeout,

    #[error("认证失败")]
    Unauthorized,

    #[error("资源未找到")]
    NotFound,

    #[error("服务器错误: {0}")]
    Server(String),
}

struct ApiClient {
    client: reqwest::Client,
    base_url: String,
}

impl ApiClient {
    fn new(base_url: String) -> Self {
        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(10))
            .build()
            .unwrap();

        ApiClient { client, base_url }
    }

    async fn get_user(&self, user_id: i32) -> Result<User, ApiError> {
        let url = format!("{}/users/{}", self.base_url, user_id);

        let response = self.client
            .get(&url)
            .send()
            .await
            .map_err(ApiError::Network)?;

        // 处理 HTTP 状态码
        match response.status().as_u16() {
            200 => {
                let user: User = response.json().await?;
                Ok(user)
            }
            401 => Err(ApiError::Unauthorized),
            404 => Err(ApiError::NotFound),
            500..=599 => Err(ApiError::Server(response.status().to_string())),
            status => {
                let text = response.text().await.unwrap_or_default();
                Err(ApiError::Http(status, text))
            }
        }
    }

    // 重试逻辑
    async fn get_user_with_retry(&self, user_id: i32, max_retries: u32) -> Result<User, ApiError> {
        let mut retries = 0;

        loop {
            match self.get_user(user_id).await {
                Ok(user) => return Ok(user),
                Err(ApiError::Network(_) | ApiError::Timeout) if retries < max_retries => {
                    retries += 1;
                    tokio::time::sleep(Duration::from_secs(1 << retries)).await; // 指数退避
                }
                Err(e) => return Err(e),
            }
        }
    }
}

struct User;

🧪 九、测试中的错误处理

#[cfg(test)]
mod tests {
    use super::*;

    // 测试正常情况
    #[test]
    fn test_success() -> Result<(), String> {
        let result = parse_and_multiply("2", "3")?;
        assert_eq!(result, 6);
        Ok(())
    }

    // 测试错误情况
    #[test]
    fn test_parse_error() {
        let result = parse_and_multiply("abc", "3");
        assert!(result.is_err());

        if let Err(msg) = result {
            assert!(msg.contains("解析 abc 失败"));
        }
    }

    // 测试 panic
    #[test]
    #[should_panic(expected = "除数不能为零")]
    fn test_divide_by_zero() {
        divide(10, 0);
    }

    #[test]
    #[should_panic]
    fn test_unwrap_panic() {
        let result: Result<i32, &str> = Err("错误");
        result.unwrap();
    }

    // 使用 assert
    #[test]
    fn test_with_assert() {
        let result = parse_and_multiply("2", "3");
        assert!(result.is_ok());

        let value = result.unwrap();
        assert_eq!(value, 6);

        assert!(parse_and_multiply("a", "3").is_err());
    }

    // 测试特定错误类型
    #[test]
    fn test_error_types() {
        use std::io;

        let io_error = io::Error::new(io::ErrorKind::NotFound, "文件未找到");
        let app_error = AppError::Io(io_error);

        match app_error {
            AppError::Io(e) => {
                assert_eq!(e.kind(), io::ErrorKind::NotFound);
            }
            _ => panic!("应该是 Io 错误"),
        }
    }
}

fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("除数不能为零");
    }
    a / b
}

⚡ 十、性能考虑

1. 避免不必要的错误分配

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

// 使用静态错误消息避免分配
#[derive(Debug)]
enum StaticError {
    InvalidInput,
    OutOfBounds,
    NetworkError,
}

impl Error for StaticError {}
impl fmt::Display for StaticError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StaticError::InvalidInput => write!(f, "无效输入"),
            StaticError::OutOfBounds => write!(f, "超出范围"),
            StaticError::NetworkError => write!(f, "网络错误"),
        }
    }
}

// 使用 Cow 避免不必要的克隆
use std::borrow::Cow;

#[derive(Debug)]
enum SmartError<'a> {
    Static(&'static str),
    Dynamic(Cow<'a, str>),
}

impl<'a> SmartError<'a> {
    fn new_static(msg: &'static str) -> Self {
        SmartError::Static(msg)
    }

    fn new_dynamic<S: Into<Cow<'a, str>>>(msg: S) -> Self {
        SmartError::Dynamic(msg.into())
    }
}

2. 错误缓存

use std::sync::OnceLock;
use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct CachedError {
    message: &'static str,
}

impl Error for CachedError {}
impl fmt::Display for CachedError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

// 使用 OnceLock 缓存常见错误
static COMMON_ERRORS: OnceLock<CommonErrors> = OnceLock::new();

struct CommonErrors {
    not_found: CachedError,
    timeout: CachedError,
    invalid_input: CachedError,
}

impl CommonErrors {
    fn global() -> &'static Self {
        COMMON_ERRORS.get_or_init(|| CommonErrors {
            not_found: CachedError { message: "未找到" },
            timeout: CachedError { message: "超时" },
            invalid_input: CachedError { message: "无效输入" },
        })
    }
}

📋 十一、错误处理最佳实践

1. 库 vs 应用程序

// 库代码:提供具体的错误类型
pub mod my_lib {
    use thiserror::Error;

    #[derive(Error, Debug)]
    pub enum LibError {
        #[error("配置错误: {0}")]
        Config(String),
        #[error("IO错误: {0}")]
        Io(#[from] std::io::Error),
    }

    pub fn library_function() -> Result<(), LibError> {
        // 库函数返回具体的错误类型
        Ok(())
    }
}

// 应用程序:使用 anyhow 简化错误处理
use anyhow::Result;

mod my_lib;

fn application_function() -> Result<()> {
    // 应用程序使用 anyhow::Result
    my_lib::library_function()?;
    Ok(())
}

2. 错误处理模式选择

fn error_handling_patterns() {
    // 模式1: 使用 match 明确处理
    let result: Result<i32, &str> = Ok(42);
    match result {
        Ok(value) => println!("成功: {}", value),
        Err(e) => println!("错误: {}", e),
    }

    // 模式2: 使用 if let
    if let Ok(value) = result {
        println!("值: {}", value);
    }

    // 模式3: 使用 map_or
    let value = result.map_or(0, |v| v);

    // 模式4: 使用 unwrap_or_else
    let value = result.unwrap_or_else(|err| {
        println!("使用默认值,错误: {}", err);
        0
    });

    // 模式5: 使用 ? 传播错误
    fn propagate() -> Result<(), &str> {
        let _ = result?;
        Ok(())
    }
}

3. 日志和监控

use log::{error, warn, info};
use anyhow::Result;

fn process_with_logging() -> Result<()> {
    // 记录不同级别的日志
    info!("开始处理");

    match risky_operation() {
        Ok(result) => {
            info!("操作成功: {:?}", result);
            Ok(())
        }
        Err(e) => {
            // 记录错误但不崩溃
            error!("操作失败: {}", e);

            // 记录完整的错误链
            let mut source = e.source();
            while let Some(err) = source {
                warn!("原因: {}", err);
                source = err.source();
            }

            // 返回错误
            Err(e)
        }
    }
}

fn risky_operation() -> Result<String> {
    // 模拟可能失败的操作
    if rand::random() {
        Ok("成功".to_string())
    } else {
        anyhow::bail!("随机失败")
    }
}

📦 十二、完整示例:配置文件加载器

use anyhow::{Context, Result, bail};
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
use log::{info, warn, error};
use thiserror::Error;

#[derive(Error, Debug)]
enum ConfigError {
    #[error("配置文件未找到")]
    NotFound,

    #[error("配置文件格式错误: {0}")]
    InvalidFormat(String),

    #[error("配置文件验证失败: {0}")]
    Validation(String),
}

#[derive(Debug, Deserialize)]
struct Config {
    server: ServerConfig,
    database: DatabaseConfig,
    logging: LoggingConfig,
}

#[derive(Debug, Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
    timeout: u64,
}

#[derive(Debug, Deserialize)]
struct DatabaseConfig {
    url: String,
    pool_size: u32,
    timeout: u64,
}

#[derive(Debug, Deserialize)]
struct LoggingConfig {
    level: String,
    file: Option<PathBuf>,
}

impl Config {
    fn validate(&self) -> Result<(), ConfigError> {
        if self.server.port == 0 {
            return Err(ConfigError::Validation("端口不能为0".to_string()));
        }

        if self.database.pool_size == 0 {
            return Err(ConfigError::Validation("数据库连接池大小不能为0".to_string()));
        }

        Ok(())
    }
}

struct ConfigLoader {
    search_paths: Vec<PathBuf>,
}

impl ConfigLoader {
    fn new() -> Self {
        let mut search_paths = vec![
            PathBuf::from("./config.toml"),
            PathBuf::from("~/.config/app/config.toml"),
            PathBuf::from("/etc/app/config.toml"),
        ];

        // 添加环境变量指定的路径
        if let Ok(env_path) = std::env::var("APP_CONFIG_PATH") {
            search_paths.insert(0, PathBuf::from(env_path));
        }

        ConfigLoader { search_paths }
    }

    fn find_config_file(&self) -> Result<PathBuf, ConfigError> {
        for path in &self.search_paths {
            if path.exists() {
                info!("找到配置文件: {:?}", path);
                return Ok(path.clone());
            }
        }

        warn!("未找到配置文件,搜索路径: {:?}", self.search_paths);
        Err(ConfigError::NotFound)
    }

    fn load(&self) -> Result<Config, anyhow::Error> {
        // 查找配置文件
        let config_path = self.find_config_file()
            .context("查找配置文件失败")?;

        // 读取文件
        let content = fs::read_to_string(&config_path)
            .with_context(|| format!("读取配置文件失败: {:?}", config_path))?;

        // 解析配置
        let config: Config = toml::from_str(&content)
            .map_err(|e| {
                ConfigError::InvalidFormat(e.to_string())
            })
            .with_context(|| format!("解析配置文件失败: {:?}", config_path))?;

        // 验证配置
        config.validate()
            .context("配置验证失败")?;

        info!("配置加载成功");
        Ok(config)
    }

    fn load_with_fallback(&self) -> Result<Config, anyhow::Error> {
        match self.load() {
            Ok(config) => Ok(config),
            Err(e) => {
                error!("加载配置失败: {}", e);

                // 尝试使用默认配置
                warn!("使用默认配置");
                self.create_default_config()
            }
        }
    }

    fn create_default_config(&self) -> Result<Config, anyhow::Error> {
        let default_config = Config {
            server: ServerConfig {
                host: "localhost".to_string(),
                port: 8080,
                timeout: 30,
            },
            database: DatabaseConfig {
                url: "postgresql://localhost:5432/app".to_string(),
                pool_size: 10,
                timeout: 5,
            },
            logging: LoggingConfig {
                level: "info".to_string(),
                file: None,
            },
        };

        // 保存默认配置
        let default_path = Path::new("./config.default.toml");
        let toml = toml::to_string_pretty(&default_config)?;
        fs::write(default_path, toml)
            .context("保存默认配置失败")?;

        info!("已创建默认配置文件: {:?}", default_path);
        Ok(default_config)
    }
}

// 主程序
fn main() -> Result<()> {
    // 初始化日志
    env_logger::init();

    // 创建配置加载器
    let loader = ConfigLoader::new();

    // 加载配置
    let config = loader.load_with_fallback()?;

    // 使用配置
    println!("服务器配置: {}:{}", config.server.host, config.server.port);
    println!("数据库配置: {}", config.database.url);
    println!("日志级别: {}", config.logging.level);

    Ok(())
}

🎯 十三、总结要点

  1. 区分错误类型:可恢复错误用 Result,不可恢复错误用 panic
  2. 自定义错误类型:使用枚举定义清晰的错误类型
  3. 使用适当的库:库代码用 thiserror,应用程序用 anyhow
  4. 提供错误上下文:使用 contextwith_context 添加信息
  5. 错误传播:合理使用 ? 运算符
  6. 错误处理策略:根据场景选择匹配、解包或传播
  7. 日志记录:记录错误以便调试和监控
  8. 性能考虑:避免不必要的错误分配
  9. 测试错误:确保错误情况被正确处理

Rust 的错误处理系统是其最强大的特性之一,通过编译时检查确保错误的正确处理,避免了未处理异常导致的问题。正确使用错误处理可以编写出既安全又健壮的程序。