Rustでのエラー設計を、境界ごとに thiserror / anyhow を使い分けて実装する。ドメイン/ライブラリは型付きエラー(thiserror)、アプリ境界のみ anyow。context付与、unwrap禁止、HTTP/CLI変換の指針を含む。
thiserror による型付きエラー(Result<T, Error>)anyhow::Result と .context() / .with_context()anyhow::Error を露出しない。String エラー」で返さない(判断不能になる)。anyhow
anyhow::Error と anyhow::Result<T> による「型消去された汎用エラー型」。thiserror
#[derive(Error)] で std::error::Error 実装を自動生成するためのクレート。ライブラリ/ドメイン層 → thiserror で意味のある Error 型を定義
アプリケーション境界(main など) → 複数の Error を anyhow でまとめて扱う
戻り値は anyhow::Result<T> を使うのは「最上位だけ」
main や CLI ハンドラ、HTTP サーバのエントリポイントなど、anyhow::Result<()> を使う。anyhow::Result を広げない。use anyhow::Result;
fn main() -> Result<()> {
app::run()?;
Ok(())
}
.context() / .with_context() でエラーに文脈を必ず付ける
use anyhow::{Context, Result};
fn load_config(path: &str) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("failed to read config from {path}"))
}
「ハンドルできない/ハンドルしない」境界でのみ anyhow に集約する
thiserror ベースのエラーを anyhow::Error に吸わせるのは OK。unwrap / expect の禁止(初期化コードなど例外的ケースを除く)
Result / Option として扱い、? と anyhow / thiserror で処理する。Public API では anyhow を返さず、自前の Error 型を定義する
pub fn ... -> Result<T, Error> の Error は自前の enum / struct。anyhow::Error を public API に出すのは禁止。use thiserror::Error;
#[derive(Debug, Error)]
pub enum RepositoryError {
#[error("db error: {0}")]
Db(#[from] sqlx::Error),
#[error("entity not found: {id}")]
NotFound { id: String },
}
pub type Result<T> = std::result::Result<T, RepositoryError>;
#[from] で外部エラーをラップし、source を保持する
#[from] を使って自動変換する。? 演算子で自然に伝搬できる。エラー型は「使う側の判断に必要な粒度」で設計する
#[derive(Debug, Error)]
pub enum DomainError {
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("external service failed: {0}")]
External(String),
#[error("unexpected internal error")]
Internal(#[from] anyhow::Error), // ← ドメイン内だけで包むのはアリ
}
Error 型はモジュール/境界ごとに分ける
Error enum に何でも詰め込まず、
「RepositoryError」「DomainError」「ApiError」のように責務ごとに分割する。thiserror エラーの Clone は条件付きで採用する
Clone を実装済みなら #[derive(Clone, Debug, Error)] を採用する。Clone が不要なエラー型にはむやみに付けない。Option<Error> の状態保持では参照アクセサを併設する
Option<Error> をキャッシュする状態型では、
不要な clone を避けるため Option<&Error> を返す参照アクセサ(例: last_ref())を用意する。last())を併存させる。Result<T, DomainError>(または責務別Error)になっている#[from] による source 保持ができている(原因追跡できる).context() / .with_context() が付与されているunwrap/expect が残っていない(例外: テスト、明示された初期化のみ)Clone が必要な thiserror エラーだけに Clone derive を付与しているOption<Error> の状態保持型で、参照アクセサ(例: last_ref())を優先して不要 clone を避けている