← 中文资源

简体中文版本

Thiserror 类型化错误枚举

用 thiserror 把领域错误建模成枚举:变体携带结构化载荷、from 源自动链式转换、Display 语义在声明处就近编写。

阅读英文原文

示例说明

字符串错误丢失结构,调用方只能匹配文本。thiserror 派生的错误枚举让每个失败模式成为一等类型:NotFound 带着缺失的 SKU、InsufficientStock 带着现有与请求量——match 可以精确分支处理而不用解析消息。#[from] 属性自动生成 From 转换,问号传播外部错误时零样板。

技术要点

  • 变体载荷即结构化的错误数据
  • from 属性自动生成转换
  • Display 就近写在变体声明处

代码解读

InventoryError 五个变体覆盖典型失败模式:两种业务错误带字段载荷、IO 与序列化经 from 转换保留源错误、过期错误带秒数。reserve 按条件返回对应变体;main 的两个 match 演示载荷解构匹配与错误链打印。fetch_manifest 里两个问号分别触发两种 From 转换。

使用建议

库边界用 thiserror 给调用方稳定 API,应用顶层用 anyhow 汇聚。变体数量增长时拆分子枚举组合。#[from] 源错误保存在链上可逐层追溯。

代码示例

use thiserror::Error;

#[derive(Debug, Error)]
pub enum InventoryError {
    #[error("sku {sku} not found in catalog")]
    NotFound { sku: String },

    #[error("insufficient stock: have {available}, requested {requested}")]
    InsufficientStock { available: u32, requested: u32 },

    #[error("warehouse service unreachable")]
    Unreachable(#[from] std::io::Error),

    #[error("serialization of manifest failed")]
    Serialization(#[from] serde_json::Error),

    #[error("reservation expired after {seconds}s")]
    Expired { seconds: u64 },
}

pub type InventoryResult<T> = Result<T, InventoryError>;

fn reserve(sku: &str, requested: u32) -> InventoryResult<u32> {
    let stock: u32 = if sku == "SKU-9" { 2 } else { 120 };

    if sku == "SKU-0" {
        return Err(InventoryError::NotFound { sku: sku.into() });
    }
    if requested > stock {
        return Err(InventoryError::InsufficientStock {
            available: stock,
            requested,
        });
    }
    Ok(stock - requested)
}

fn fetch_manifest(path: &str) -> InventoryResult<String> {
    let raw = std::fs::read_to_string(path)?; // io::Error 自动 From 转换
    let value: serde_json::Value = serde_json::from_str(&raw)?;
    Ok(value.to_string())
}

fn main() {
    match reserve("SKU-9", 5) {
        Ok(left) => println!("reserved, {left} left"),
        Err(err) => println!("reserve failed: {err}"),
    }

    match reserve("SKU-0", 1) {
        Ok(_) => unreachable!(),
        Err(InventoryError::NotFound { sku }) => println!("missing sku: {sku}"),
        Err(other) => println!("other: {other}"),
    }

    println!("{:?}", fetch_manifest("missing.json").map(|s| s.len()));
}