第十篇:Rust 错误处理

0 阅读15分钟

Rust 错误处理

Rust 的错误处理系统通过类型系统在编译期强制处理错误,避免运行时未捕获异常,是 Rust 安全性的核心特性之一。

目录

  1. 错误处理基础
  2. Result 类型
  3. Option 类型
  4. ? 操作符
  5. 自定义错误类型
  6. 错误传播
  7. 错误转换
  8. 常用错误处理库
  9. 实战示例
  10. 最佳实践

错误处理基础

Rust vs 其他语言

语言错误处理方式问题
Java/Pythontry-catch 异常可能忘记捕获
Go错误返回值可能忘记检查
C错误码容易被忽略
RustResult/Option + 类型系统编译期强制处理 ✅

Rust 错误分类

// 1. 可恢复错误(Recoverable Errors)
// 使用 Result<T, E>
fn read_file(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

// 2. 不可恢复错误(Unrecoverable Errors)
// 使用 panic!
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("除数不能为零!");
    }
    a / b
}

Result 类型

1. Result 定义

// 标准库定义
enum Result<T, E> {
    Ok(T),      // 成功,包含值
    Err(E),     // 失败,包含错误
}

2. 基本使用

use std::fs::File;
use std::io::Read;

fn read_username_from_file() -> Result<String, std::io::Error> {
    let mut file = File::open("username.txt")?;
    let mut username = String::new();
    file.read_to_string(&mut username)?;
    Ok(username)
}

fn main() {
    match read_username_from_file() {
        Ok(username) => println!("用户名: {}", username),
        Err(e) => println!("错误: {}", e),
    }
}

3. Result 方法速查

fn example() {
    let result: Result<i32, &str> = Ok(42);
    
    // ===== 检查状态 =====
    assert!(result.is_ok());
    assert!(!result.is_err());
    
    // ===== 获取值 =====
    
    // unwrap - 成功返回值,失败 panic
    let value = result.unwrap();  // 42
    
    // unwrap_or - 失败返回默认值
    let value = result.unwrap_or(0);  // 42
    
    // unwrap_or_else - 失败执行闭包
    let value = result.unwrap_or_else(|e| {
        println!("错误: {}", e);
        0
    });
    
    // expect - 带自定义消息的 unwrap
    let value = result.expect("应该有值");
    
    // unwrap_or_default - 使用类型的默认值
    let value = result.unwrap_or_default();  // 0 (i32::default())
    
    // ===== 转换 =====
    
    // map - 转换 Ok 值
    let doubled = result.map(|x| x * 2);  // Ok(84)
    
    // map_err - 转换 Err 值
    let mapped_err = result.map_err(|e| format!("错误: {}", e));
    
    // and_then (flatMap) - 链式调用
    let chained = result.and_then(|x| Ok(x * 2));
    
    // or_else - 错误恢复
    let recovered = result.or_else(|_| Ok(0));
    
    // ===== 组合 =====
    
    // and - 两个都成功才成功
    let r1: Result<i32, &str> = Ok(1);
    let r2: Result<i32, &str> = Ok(2);
    let combined = r1.and(r2);  // Ok(2)
    
    // or - 有一个成功就成功
    let r1: Result<i32, &str> = Err("错误1");
    let r2: Result<i32, &str> = Ok(2);
    let combined = r1.or(r2);  // Ok(2)
}

4. 实用模式

模式匹配
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("除数不能为零"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 2) {
        Ok(result) => println!("结果: {}", result),
        Err(e) => println!("错误: {}", e),
    }
}
if let 简化
fn main() {
    let result = divide(10, 0);
    
    // 只关心成功情况
    if let Ok(value) = result {
        println!("结果: {}", value);
    }
    
    // 只关心失败情况
    if let Err(e) = result {
        println!("错误: {}", e);
    }
}
链式调用
use std::fs::File;
use std::io::{self, Read};

fn read_file() -> Result<String, io::Error> {
    File::open("data.txt")
        .and_then(|mut file| {
            let mut contents = String::new();
            file.read_to_string(&mut contents)?;
            Ok(contents)
        })
        .map(|s| s.trim().to_string())
}

Option 类型

1. Option 定义

// 标准库定义
enum Option<T> {
    Some(T),    // 有值
    None,       // 无值
}

2. 基本使用

fn find_user(id: u64) -> Option<String> {
    if id == 1 {
        Some(String::from("Alice"))
    } else {
        None
    }
}

fn main() {
    match find_user(1) {
        Some(name) => println!("找到用户: {}", name),
        None => println!("用户不存在"),
    }
}

3. Option 方法速查

fn example() {
    let some_value: Option<i32> = Some(42);
    let none_value: Option<i32> = None;
    
    // ===== 检查状态 =====
    assert!(some_value.is_some());
    assert!(some_value.is_none() == false);
    
    // ===== 获取值 =====
    
    // unwrap - 有值返回,None panic
    let value = some_value.unwrap();  // 42
    
    // unwrap_or - None 返回默认值
    let value = none_value.unwrap_or(0);  // 0
    
    // unwrap_or_else - None 执行闭包
    let value = none_value.unwrap_or_else(|| {
        println!("计算默认值");
        0
    });
    
    // expect - 带自定义消息
    let value = some_value.expect("应该有值");
    
    // ===== 转换 =====
    
    // map - 转换 Some 值
    let doubled = some_value.map(|x| x * 2);  // Some(84)
    
    // map_or - 提供默认值的 map
    let doubled = none_value.map_or(0, |x| x * 2);  // 0
    
    // and_then - 链式调用
    let chained = some_value.and_then(|x| Some(x * 2));
    
    // filter - 过滤
    let filtered = some_value.filter(|x| *x > 40);  // Some(42)
    
    // ===== 组合 =====
    
    // and - 两个都有值才返回第二个
    let a = Some(1);
    let b = Some(2);
    assert_eq!(a.and(b), Some(2));
    
    // or - 有一个有值就返回
    let a: Option<i32> = None;
    let b = Some(2);
    assert_eq!(a.or(b), Some(2));
    
    // ===== 转换为 Result =====
    let result: Result<i32, &str> = some_value.ok_or("没有值");
    assert_eq!(result, Ok(42));
}

4. 实用模式

解构
fn main() {
    let maybe_name = Some(String::from("Alice"));
    
    // if let
    if let Some(name) = maybe_name {
        println!("你好, {}", name);
    }
    
    // while let
    let mut stack = vec![Some(1), Some(2), Some(3), None];
    while let Some(Some(value)) = stack.pop() {
        println!("{}", value);
    }
}
链式调用
fn get_user_email(user_id: u64) -> Option<String> {
    find_user(user_id)
        .and_then(|user| user.email)
        .map(|email| email.to_lowercase())
        .filter(|email| email.contains("@"))
}

? 操作符

1. 基本用法

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

// 不使用 ? 操作符
fn read_file_old() -> Result<String, io::Error> {
    let mut file = match File::open("data.txt") {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    
    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => Ok(contents),
        Err(e) => Err(e),
    }
}

// 使用 ? 操作符(简洁!)
fn read_file_new() -> Result<String, io::Error> {
    let mut file = File::open("data.txt")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// 更简洁的版本
fn read_file_shortest() -> Result<String, io::Error> {
    std::fs::read_to_string("data.txt")
}

2. ? 操作符工作原理

// 这段代码:
let result = some_function()?;

// 等价于:
let result = match some_function() {
    Ok(value) => value,
    Err(e) => return Err(e.into()),  // 注意 .into() 自动转换
};

3. 在 Option 中使用 ?

fn add_last_two(numbers: &[i32]) -> Option<i32> {
    let last = numbers.last()?;           // None 则提前返回
    let second_last = numbers.get(numbers.len() - 2)?;
    Some(last + second_last)
}

fn main() {
    assert_eq!(add_last_two(&[1, 2, 3]), Some(5));
    assert_eq!(add_last_two(&[1]), None);
    assert_eq!(add_last_two(&[]), None);
}

4. main 函数中使用 ?

use std::error::Error;
use std::fs::File;

// main 返回 Result
fn main() -> Result<(), Box<dyn Error>> {
    let file = File::open("data.txt")?;
    println!("文件打开成功");
    Ok(())
}

// 或者简单的错误类型
fn main() -> Result<(), String> {
    let data = read_data().map_err(|e| e.to_string())?;
    process(data)?;
    Ok(())
}

自定义错误类型

1. 简单枚举错误

#[derive(Debug)]
enum MathError {
    DivisionByZero,
    NegativeSquareRoot,
    Overflow,
}

fn divide(a: i32, b: i32) -> Result<i32, MathError> {
    if b == 0 {
        Err(MathError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}

fn sqrt(n: i32) -> Result<f64, MathError> {
    if n < 0 {
        Err(MathError::NegativeSquareRoot)
    } else {
        Ok((n as f64).sqrt())
    }
}

fn main() {
    match divide(10, 0) {
        Ok(result) => println!("结果: {}", result),
        Err(MathError::DivisionByZero) => println!("错误: 除数不能为零"),
        Err(e) => println!("其他错误: {:?}", e),
    }
}

2. 带数据的错误

#[derive(Debug)]
enum ParseError {
    InvalidFormat(String),
    OutOfRange { value: i32, min: i32, max: i32 },
    MissingField(&'static str),
}

fn parse_age(s: &str) -> Result<u32, ParseError> {
    let age = s.parse::<i32>()
        .map_err(|_| ParseError::InvalidFormat(s.to_string()))?;
    
    if age < 0 || age > 150 {
        return Err(ParseError::OutOfRange {
            value: age,
            min: 0,
            max: 150,
        });
    }
    
    Ok(age as u32)
}

fn main() {
    match parse_age("200") {
        Ok(age) => println!("年龄: {}", age),
        Err(ParseError::OutOfRange { value, min, max }) => {
            println!("年龄 {} 超出范围 {}-{}", value, min, max);
        }
        Err(e) => println!("错误: {:?}", e),
    }
}

3. 实现 Error trait

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

#[derive(Debug)]
struct MyError {
    message: String,
    code: i32,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "错误 [{}]: {}", self.code, self.message)
    }
}

impl Error for MyError {}

impl MyError {
    fn new(message: &str, code: i32) -> Self {
        MyError {
            message: message.to_string(),
            code,
        }
    }
}

fn do_something() -> Result<(), MyError> {
    Err(MyError::new("操作失败", 500))
}

fn main() {
    match do_something() {
        Ok(_) => println!("成功"),
        Err(e) => {
            println!("发生错误: {}", e);
            println!("错误码: {}", e.code);
        }
    }
}

4. 使用 thiserror(推荐)

use thiserror::Error;

#[derive(Error, Debug)]
enum DataStoreError {
    #[error("数据未找到: {0}")]
    NotFound(String),
    
    #[error("数据库连接失败")]
    ConnectionError(#[from] sqlx::Error),
    
    #[error("序列化失败")]
    SerializationError(#[from] serde_json::Error),
    
    #[error("无效的输入: {field} = {value}")]
    InvalidInput {
        field: String,
        value: String,
    },
    
    #[error("未知错误")]
    Unknown,
}

fn find_user(id: i64) -> Result<User, DataStoreError> {
    if id <= 0 {
        return Err(DataStoreError::InvalidInput {
            field: "id".to_string(),
            value: id.to_string(),
        });
    }
    
    // 自动转换错误
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = ?", id)
        .fetch_one(pool)
        .await?;  // sqlx::Error 自动转换为 DataStoreError
    
    Ok(user)
}

错误传播

1. 手动传播

fn read_config() -> Result<Config, std::io::Error> {
    let content = read_file("config.toml")?;
    let config = parse_config(&content)?;
    Ok(config)
}

fn process_request() -> Result<Response, Box<dyn std::error::Error>> {
    let config = read_config()?;  // io::Error 自动转换
    let data = fetch_data(&config)?;  // 其他错误也能传播
    Ok(Response::new(data))
}

2. 错误链

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

#[derive(Debug)]
struct HighLevelError {
    message: String,
    source: Option<Box<dyn Error>>,
}

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

impl Error for HighLevelError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref())
    }
}

fn low_level() -> Result<(), std::io::Error> {
    std::fs::read_to_string("missing.txt")?;
    Ok(())
}

fn high_level() -> Result<(), HighLevelError> {
    low_level().map_err(|e| HighLevelError {
        message: "高层操作失败".to_string(),
        source: Some(Box::new(e)),
    })?;
    Ok(())
}

fn main() {
    if let Err(e) = high_level() {
        println!("错误: {}", e);
        
        let mut current_error: &dyn Error = &e;
        while let Some(source) = current_error.source() {
            println!("  原因: {}", source);
            current_error = source;
        }
    }
}

错误转换

1. From trait 自动转换

use std::io;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Parse(ParseIntError),
}

// 实现 From trait
impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError::Io(error)
    }
}

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

fn read_number() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("number.txt")?;  // 自动转换
    let number = content.trim().parse()?;  // 自动转换
    Ok(number)
}

2. map_err 手动转换

fn process() -> Result<String, String> {
    let content = std::fs::read_to_string("data.txt")
        .map_err(|e| format!("读取文件失败: {}", e))?;
    
    let parsed: i32 = content.trim().parse()
        .map_err(|e| format!("解析失败: {}", e))?;
    
    Ok(format!("值: {}", parsed))
}

3. anyhow 简化错误处理

use anyhow::{Result, Context};

fn read_config() -> Result<Config> {
    let content = std::fs::read_to_string("config.toml")
        .context("读取配置文件失败")?;
    
    let config: Config = toml::from_str(&content)
        .context("解析配置失败")?;
    
    Ok(config)
}

fn main() -> Result<()> {
    let config = read_config()
        .context("初始化配置时出错")?;
    
    println!("配置加载成功");
    Ok(())
}

常用错误处理库

1. thiserror - 定义错误类型

use thiserror::Error;

#[derive(Error, Debug)]
pub enum UserServiceError {
    #[error("用户不存在: {0}")]
    UserNotFound(i64),
    
    #[error("用户名已存在: {0}")]
    UsernameExists(String),
    
    #[error("数据库错误")]
    DatabaseError(#[from] sqlx::Error),
    
    #[error("缓存错误")]
    CacheError(#[from] redis::RedisError),
    
    #[error("无效的邮箱格式: {0}")]
    InvalidEmail(String),
}

2. anyhow - 应用层错误处理

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

fn process_user(id: i64) -> Result<()> {
    // ensure! 类似 assert
    ensure!(id > 0, "ID 必须为正数");
    
    let user = find_user(id)
        .context(format!("查找用户 {} 失败", id))?;
    
    // bail! 提前返回错误
    if !user.is_active {
        bail!("用户 {} 未激活", id);
    }
    
    Ok(())
}

3. eyre - anyhow 的增强版

use eyre::{Result, WrapErr};

fn load_config() -> Result<Config> {
    let path = "config.toml";
    let content = std::fs::read_to_string(path)
        .wrap_err_with(|| format!("读取 {} 失败", path))?;
    
    toml::from_str(&content)
        .wrap_err("解析配置失败")
}

实战示例

示例 1:文件操作

use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;

fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn write_file(path: &str, content: &str) -> io::Result<()> {
    let mut file = File::create(path)?;
    file.write_all(content.as_bytes())?;
    Ok(())
}

fn copy_file(from: &str, to: &str) -> io::Result<()> {
    let content = read_file(from)?;
    write_file(to, &content)?;
    Ok(())
}

fn main() -> io::Result<()> {
    match copy_file("input.txt", "output.txt") {
        Ok(_) => println!("文件复制成功"),
        Err(e) => eprintln!("错误: {}", e),
    }
    Ok(())
}

示例 2:HTTP 请求

use reqwest;
use serde::Deserialize;
use thiserror::Error;

#[derive(Error, Debug)]
enum ApiError {
    #[error("网络请求失败")]
    RequestFailed(#[from] reqwest::Error),
    
    #[error("JSON 解析失败")]
    ParseError(#[from] serde_json::Error),
    
    #[error("API 返回错误: {0}")]
    ApiError(String),
}

#[derive(Deserialize)]
struct User {
    id: i64,
    name: String,
}

async fn fetch_user(id: i64) -> Result<User, ApiError> {
    let url = format!("https://api.example.com/users/{}", id);
    let response = reqwest::get(&url).await?;
    
    if !response.status().is_success() {
        return Err(ApiError::ApiError(
            format!("HTTP {}", response.status())
        ));
    }
    
    let user = response.json::<User>().await?;
    Ok(user)
}

#[tokio::main]
async fn main() -> Result<(), ApiError> {
    let user = fetch_user(1).await?;
    println!("用户: {}", user.name);
    Ok(())
}

示例 3:数据库操作

use sqlx::{MySqlPool, FromRow};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DatabaseError {
    #[error("用户不存在: {0}")]
    UserNotFound(i64),
    
    #[error("SQL 错误")]
    SqlxError(#[from] sqlx::Error),
}

#[derive(FromRow)]
struct User {
    id: i64,
    username: String,
    email: String,
}

async fn get_user(
    pool: &MySqlPool,
    id: i64,
) -> Result<User, DatabaseError> {
    let user = sqlx::query_as!(
        User,
        "SELECT id, username, email FROM users WHERE id = ?",
        id
    )
    .fetch_optional(pool)
    .await?
    .ok_or(DatabaseError::UserNotFound(id))?;
    
    Ok(user)
}

async fn create_user(
    pool: &MySqlPool,
    username: &str,
    email: &str,
) -> Result<i64, DatabaseError> {
    let result = sqlx::query!(
        "INSERT INTO users (username, email) VALUES (?, ?)",
        username,
        email
    )
    .execute(pool)
    .await?;
    
    Ok(result.0 as i64)
}

示例 4:本项目用户服务

// src/services/user_service.rs
use sqlx::MySqlPool;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum UserServiceError {
    #[error("用户不存在: {0}")]
    UserNotFound(i64),
    
    #[error("用户名已存在: {0}")]
    UsernameExists(String),
    
    #[error("邮箱已存在: {0}")]
    EmailExists(String),
    
    #[error("数据库错误: {0}")]
    DatabaseError(#[from] sqlx::Error),
    
    #[error("缓存错误: {0}")]
    CacheError(#[from] redis::RedisError),
    
    #[error("密码哈希失败")]
    HashError,
}

pub struct UserService {
    pool: MySqlPool,
    cache: RedisCache,
}

impl UserService {
    pub fn new(pool: MySqlPool, cache: RedisCache) -> Self {
        Self { pool, cache }
    }
    
    pub async fn create_user(
        &self,
        username: String,
        email: String,
        password: String,
    ) -> Result<User, UserServiceError> {
        // 检查用户名是否存在
        if self.username_exists(&username).await? {
            return Err(UserServiceError::UsernameExists(username));
        }
        
        // 检查邮箱是否存在
        if self.email_exists(&email).await? {
            return Err(UserServiceError::EmailExists(email));
        }
        
        // 哈希密码
        let password_hash = hash_password(&password)
            .map_err(|_| UserServiceError::HashError)?;
        
        // 插入数据库
        let result = sqlx::query!(
            r#"
            INSERT INTO users (username, email, password_hash)
            VALUES (?, ?, ?)
            "#,
            username,
            email,
            password_hash
        )
        .execute(&self.pool)
        .await?;
        
        let user_id = result.0 as i64;
        
        // 获取完整用户信息
        self.get_user(user_id).await?
            .ok_or(UserServiceError::UserNotFound(user_id))
    }
    
    pub async fn get_user(
        &self,
        user_id: i64,
    ) -> Result<Option<User>, UserServiceError> {
        let cache_key = format!("user:{}", user_id);
        
        // 先查缓存
        if let Some(cached) = self.cache.get(&cache_key).await? {
            if let Ok(user) = serde_json::from_str::<User>(&cached) {
                return Ok(Some(user));
            }
        }
        
        // 查数据库
        let user = sqlx::query_as!(
            User,
            r#"
            SELECT id, username, email, password_hash, created_at, updated_at
            FROM users
            WHERE id = ?
            "#,
            user_id
        )
        .fetch_optional(&self.pool)
        .await?;
        
        // 写入缓存
        if let Some(ref u) = user {
            if let Ok(json) = serde_json::to_string(u) {
                let _ = self.cache.set(&cache_key, &json, 3600).await;
            }
        }
        
        Ok(user)
    }
    
    pub async fn delete_user(
        &self,
        user_id: i64,
    ) -> Result<bool, UserServiceError> {
        let result = sqlx::query!(
            "DELETE FROM users WHERE id = ?",
            user_id
        )
        .execute(&self.pool)
        .await?;
        
        // 删除缓存
        let cache_key = format!("user:{}", user_id);
        let _ = self.cache.delete(&cache_key).await;
        
        Ok(result.rows_affected() > 0)
    }
    
    async fn username_exists(&self, username: &str) -> Result<bool, sqlx::Error> {
        let count: (i64,) = sqlx::query_as(
            "SELECT COUNT(*) FROM users WHERE username = ?"
        )
        .bind(username)
        .fetch_one(&self.pool)
        .await?;
        
        Ok(count.0 > 0)
    }
    
    async fn email_exists(&self, email: &str) -> Result<bool, sqlx::Error> {
        let count: (i64,) = sqlx::query_as(
            "SELECT COUNT(*) FROM users WHERE email = ?"
        )
        .bind(email)
        .fetch_one(&self.pool)
        .await?;
        
        Ok(count.0 > 0)
    }
}

示例 5:HTTP 控制器错误处理

// src/controllers/user_controller.rs
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;

// 为服务错误实现 IntoResponse
impl IntoResponse for UserServiceError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            UserServiceError::UserNotFound(id) => (
                StatusCode::NOT_FOUND,
                format!("用户 {} 不存在", id)
            ),
            UserServiceError::UsernameExists(username) => (
                StatusCode::CONFLICT,
                format!("用户名 {} 已存在", username)
            ),
            UserServiceError::EmailExists(email) => (
                StatusCode::CONFLICT,
                format!("邮箱 {} 已存在", email)
            ),
            UserServiceError::HashError => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "密码处理失败".to_string()
            ),
            UserServiceError::DatabaseError(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "数据库错误".to_string()
            ),
            UserServiceError::CacheError(_) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "缓存错误".to_string()
            ),
        };
        
        let body = Json(json!({
            "error": message,
        }));
        
        (status, body).into_response()
    }
}

pub async fn create_user(
    State(service): State<Arc<UserService>>,
    Json(payload): Json<CreateUserRequest>,
) -> Result<Json<User>, UserServiceError> {
    let user = service.create_user(
        payload.username,
        payload.email,
        payload.password,
    ).await?;
    
    Ok(Json(user))
}

pub async fn get_user(
    State(service): State<Arc<UserService>>,
    Path(user_id): Path<i64>,
) -> Result<Json<User>, UserServiceError> {
    let user = service.get_user(user_id).await?
        .ok_or(UserServiceError::UserNotFound(user_id))?;
    
    Ok(Json(user))
}

示例 6:组合多种错误

use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("IO 错误: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("解析错误: {0}")]
    Parse(#[from] std::num::ParseIntError),
    
    #[error("HTTP 错误: {0}")]
    Http(#[from] reqwest::Error),
    
    #[error("数据库错误: {0}")]
    Database(#[from] sqlx::Error),
    
    #[error("业务逻辑错误: {0}")]
    Business(String),
}

async fn complex_operation() -> Result<String, AppError> {
    // IO 操作
    let config = std::fs::read_to_string("config.txt")?;
    
    // 解析操作
    let port: u16 = config.trim().parse()?;
    
    // HTTP 请求
    let response = reqwest::get("https://api.example.com/data").await?;
    let data = response.text().await?;
    
    // 数据库操作
    let db = get_db_pool().await;
    sqlx::query("INSERT INTO logs (data) VALUES (?)")
        .bind(&data)
        .execute(&db)
        .await?;
    
    // 业务逻辑
    if data.is_empty() {
        return Err(AppError::Business("数据为空".to_string()));
    }
    
    Ok(data)
}

最佳实践

1. 错误类型选择

// ✅ 库代码:使用具体错误类型
pub enum MyLibError {
    InvalidInput,
    ConnectionFailed,
}

pub fn lib_function() -> Result<T, MyLibError> { }

// ✅ 应用代码:使用 anyhow
use anyhow::Result;

fn app_function() -> Result<T> { }

// ❌ 避免:过度使用 Box<dyn Error>
fn bad() -> Result<T, Box<dyn std::error::Error>> { }

2. 错误消息

// ✅ 好的错误消息
#[error("无法连接数据库 {host}:{port}")]
ConnectionFailed { host: String, port: u16 }

#[error("用户 {username} (ID: {id}) 不存在")]
UserNotFound { id: i64, username: String }

// ❌ 不好的错误消息
#[error("错误")]
Error

#[error("失败")]
Failed

3. 何时使用 unwrap/expect

// ✅ 可以使用的场景

// 1. 测试代码
#[test]
fn test_something() {
    let value = some_function().unwrap();
    assert_eq!(value, 42);
}

// 2. 确定不会失败
let number: i32 = "42".parse().expect("硬编码的数字");

// 3. 原型开发(但要留 TODO)
let config = load_config().expect("TODO: 添加错误处理");

// ❌ 避免在生产代码中使用
fn production_code() -> Result<()> {
    let file = File::open("data.txt").unwrap();  // ❌ 不要这样
    let file = File::open("data.txt")?;          // ✅ 这样做
    Ok(())
}

4. 错误传播策略

// ✅ 使用 ? 操作符
fn good() -> Result<String, io::Error> {
    let content = std::fs::read_to_string("file.txt")?;
    Ok(content.to_uppercase())
}

// ❌ 手动 match(冗长)
fn bad() -> Result<String, io::Error> {
    match std::fs::read_to_string("file.txt") {
        Ok(content) => Ok(content.to_uppercase()),
        Err(e) => Err(e),
    }
}

5. 添加上下文

use anyhow::{Context, Result};

fn process_config() -> Result<Config> {
    let content = std::fs::read_to_string("config.toml")
        .context("无法读取配置文件 config.toml")?;
    
    let config: Config = toml::from_str(&content)
        .context("配置文件格式错误")?;
    
    validate_config(&config)
        .context("配置验证失败")?;
    
    Ok(config)
}

6. 日志记录

use tracing::{error, warn, info};

async fn handle_request(id: i64) -> Result<Response, AppError> {
    match process_request(id).await {
        Ok(response) => {
            info!(request_id = id, "请求处理成功");
            Ok(response)
        }
        Err(e) => {
            error!(
                request_id = id,
                error = %e,
                "请求处理失败"
            );
            Err(e)
        }
    }
}

7. 优雅降级

async fn get_user_with_fallback(id: i64) -> User {
    match fetch_from_cache(id).await {
        Ok(user) => user,
        Err(e) => {
            warn!("缓存获取失败: {}, 降级到数据库", e);
            match fetch_from_db(id).await {
                Ok(user) => user,
                Err(e) => {
                    error!("数据库获取也失败: {}, 返回默认用户", e);
                    User::default()
                }
            }
        }
    }
}

8. 重试逻辑

use tokio::time::{sleep, Duration};

async fn retry_operation<F, T, E>(
    mut operation: F,
    max_retries: u32,
) -> Result<T, E>
where
    F: FnMut() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<T, E>>>>,
{
    let mut attempts = 0;
    
    loop {
        match operation().await {
            Ok(result) => return Ok(result),
            Err(e) => {
                attempts += 1;
                if attempts >= max_retries {
                    return Err(e);
                }
                
                let delay = Duration::from_secs(2u64.pow(attempts));
                warn!("操作失败,{}秒后重试 ({}/{})", delay.as_secs(), attempts, max_retries);
                sleep(delay).await;
            }
        }
    }
}

总结

错误处理速查表

场景使用示例
可能失败的操作Result<T, E>File::open()
可能没有值Option<T>vec.get(index)
传播错误? 操作符let x = f()?;
库开发thiserror自定义错误类型
应用开发anyhow简化错误处理
不可恢复panic!断言失败

Result vs Option

// Result - 需要知道失败原因
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("除数不能为零".to_string())
    } else {
        Ok(a / b)
    }
}

// Option - 只需要知道有没有值
fn find_user(id: i64) -> Option<User> {
    // ...
}

关键原则

  1. 优先使用 Result/Option,避免 panic
  2. 使用 ?  简化错误传播
  3. 为库定义具体错误,为应用使用 anyhow
  4. 添加错误上下文,便于调试
  5. 记录错误日志,便于排查
  6. 生产代码避免 unwrap,除非确定安全

本项目错误处理模式

// 1. 定义服务错误
#[derive(Error, Debug)]
pub enum UserServiceError {
    #[error("用户不存在: {0}")]
    UserNotFound(i64),
    
    #[error("数据库错误")]
    DatabaseError(#[from] sqlx::Error),
}

// 2. 服务层返回 Result
impl UserService {
    pub async fn get_user(&self, id: i64) -> Result<User, UserServiceError> {
        let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = ?", id)
            .fetch_optional(&self.pool)
            .await?
            .ok_or(UserServiceError::UserNotFound(id))?;
        
        Ok(user)
    }
}

// 3. 控制器处理错误并转换为 HTTP 响应
impl IntoResponse for UserServiceError {
    fn into_response(self) -> Response {
        match self {
            UserServiceError::UserNotFound(_) => {
                (StatusCode::NOT_FOUND, self.to_string()).into_response()
            }
            UserServiceError::DatabaseError(_) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "服务器错误").into_response()
            }
        }
    }
}

延伸阅读


记住:Rust 的错误处理看起来繁琐,但它强制你在编译期处理所有错误情况,避免运行时崩溃!