4 分で読了 #rust #error-handling
このページの目次

Error trait とエコシステム

std::error::Error は Display+Debug+source() の組み合わせに過ぎず、薄すぎる。thiserror は derive マクロを使ってライブラリに構造化されたエラー型を生成し(呼び出し元が match 可能)、anyhow はアプリケーション向けに「エラー型を気にせず、ただ上に伝播させる」万能なエラーラッパーを提供する。ライブラリには thiserror、アプリケーションには anyhow——これは教義ではなく、実践から蓄積された役割分担である。

std::error::Error trait

標準ライブラリの Error trait は非常にシンプルだ——Display + Debug + source()

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

#[derive(Debug)]
struct MyError { msg: String, source: Option<Box<dyn Error + 'static>> }

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

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

source() メソッドは根本原因を返す——これによりエラーチェーンが形成される。anyhoweyre はこのチェーンを利用してバックトレースを自動生成する。

thiserror: ライブラリクレートの derive マクロ

ライブラリ向け——呼び出し元にマッチ可能な具体的なエラー型を提供する:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data not found: {0}")]
    NotFound(String),
    #[error("IO error")]
    Io(#[from] std::io::Error),      // #[from] = From の自動実装 + 自動 source
    #[error("invalid config key={key}")]
    InvalidConfig { key: String, detail: String },
}

#[from]From<io::Error> for DataStoreError を自動生成し、? による自動変換を可能にする。呼び出し元は match を使ってエラー型を区別できる。

anyhow: アプリケーションコードの汎用エラーコンテナ

アプリケーション/バイナリ向け——エラーチェーンを収集し、具体的な型にはこだわらない:

use anyhow::{Context, Result};

fn read_config(path: &str) -> Result<Config> {
    let file = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {}", path))?;
    let config: Config = toml::from_str(&file)
        .context("invalid TOML format")?;
    Ok(config)
}

anyhow::Error は任意の Error + Send + Sync + 'static を格納できる。context() はエラーチェーンに人間が読める注釈を追加する——これが anyhow で最もよく使われるパターンだ。

thiserror と anyhow の使い分け

シンプルなルールがある:⁠ライブラリは thiserror(呼び出し元に選択肢を)、アプリケーションは anyhow(エラーの文脈を収集)。ライブラリが呼び出し元に特定のエラー報告ライブラリを強制すべきではない——具体的でマッチ可能な型を投げればいい。アプリケーションはエラー型を区別する必要はない——必要なのは、すべてのエラーがキャッチされ、記録され、バグを再現するのに十分な文脈を持っていることだ。

参考

  • thiserror: docs.rs/thiserror
  • anyhow: docs.rs/anyhow

Keywords: Error trait, thiserror, anyhow, context, source, error chain, #[from]