m06-error-handling について
Rust 開発者向けに、ライブラリやアプリのエラー処理を設計するスキルです。想定内の失敗とバグを区別し、適切な型や伝播方法、コンテキストを選ぶ手助けをします。
主な機能
- 01想定内の失敗とバグの分類
- 02Result、Option、panic の選択
- 03ライブラリとアプリのエラー処理比較
- 04エラー伝播時のコンテキスト保持
活用シーン
- 01API のエラー戻り値の設計
- 02危険な unwrap の置き換え
- 03伝播するエラーへの情報追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m06-error-handlingインストール後、Claude Code で/m06-error-handlingを入力して読み込まれたか確認します。
同梱ファイルexamples/library-vs-app.mdpatterns/error-patterns.md
詳細
エラー処理
レイヤー 1:言語の仕組み
中心となる問い
この失敗は想定内ですか、それともバグですか?
エラー処理の戦略を選ぶ前に:
- 通常の動作中に失敗する可能性はありますか?
- この失敗は誰が処理すべきですか?
- 呼び出し元にはどのようなコンテキストが必要ですか?
エラー → 設計上の問い
| パターン | ただ「こうする」と言うのではなく | 代わりに問いかけること |
|---|---|---|
| unwrap によるパニック | 「? を使う」 | ここで None/Err が実際に起こり得ますか? |
? の型の不一致 | 「anyhow を使う」 | エラー型は適切に設計されていますか? |
| エラーコンテキストの喪失 | 「.context() を追加する」 | 呼び出し元は何を知る必要がありますか? |
| エラーの種類が多すぎる | 「Box を使う」 | エラーの粒度は適切ですか? |
思考のヒント
エラーを処理する前に:
-
これはどのような種類の失敗ですか?
- 想定内 → Result<T, E>
- 不在が通常の状態 → Option
- バグ/不変条件 → panic!
- 回復不能 → panic!
-
誰が処理しますか?
- 呼び出し元 →
?で伝播する - 現在の関数 → match/if-let
- ユーザー → わかりやすいエラーメッセージ
- プログラマー → メッセージ付きでパニックする
- 呼び出し元 →
-
どのようなコンテキストが必要ですか?
- エラーの種類 → thiserror のバリアント
- 呼び出しチェーン → anyhow::Context
- デバッグ情報 → anyhow または tracing
上位をたどる ↑
エラー戦略が明確でない場合:
"Should I return Result or Option?"
↑ Ask: Is absence/failure normal or exceptional?
↑ Check: m09-domain (what does domain say?)
↑ Check: domain-* (error handling requirements)
| 状況 | たどる先 | 問い |
|---|---|---|
| unwrap が多すぎる | m09-domain | データモデルは適切ですか? |
| エラーコンテキストの設計 | m13-domain-error | どのような復旧が必要ですか? |
| ライブラリとアプリのエラー | m11-ecosystem | 利用者は誰ですか? |
下位をたどる ↓
設計から実装へ:
"Expected failure, library code"
↓ Use: thiserror for typed errors
"Expected failure, application code"
↓ Use: anyhow for ergonomic errors
"Absence is normal (find, get, lookup)"
↓ Use: Option<T>
"Bug or invariant violation"
↓ Use: panic!, assert!, unreachable!
"Need to propagate with context"
↓ Use: .context("what was happening")
クイックリファレンス
| パターン | 使用する場面 | 例 |
|---|---|---|
Result<T, E> | 回復可能なエラー | fn read() -> Result<String, io::Error> |
Option<T> | 不在が通常の状態 | fn find() -> Option<&Item> |
? | エラーを伝播する | let data = file.read()?; |
unwrap() | 開発/テスト時のみ | config.get("key").unwrap() |
expect() | 不変条件が成立する場合 | env.get("HOME").expect("HOME set") |
panic! | 回復不能 | panic!("critical failure") |
ライブラリとアプリケーション
| コンテキスト | エラークレート | 理由 |
|---|---|---|
| ライブラリ | thiserror | 利用者向けの型付きエラー |
| アプリケーション | anyhow | 人間工学的なエラー処理 |
| 混在 | 両方 | 境界では thiserror、内部では anyhow |
判断フローチャート
Is failure expected?
├─ Yes → Is absence the only "failure"?
│ ├─ Yes → Option<T>
│ └─ No → Result<T, E>
│ ├─ Library → thiserror
│ └─ Application → anyhow
└─ No → Is it a bug?
├─ Yes → panic!, assert!
└─ No → Consider if really unrecoverable
Use ? → Need context?
├─ Yes → .context("message")
└─ No → Plain ?
よくあるエラー
| エラー | 原因 | 修正方法 |
|---|---|---|
unwrap() によるパニック | None/Err が未処理 | ? または match を使う |
| 型の不一致 | エラー型が異なる | anyhow または From を使う |
| コンテキストの喪失 | コンテキストなしの ? | .context() を追加する |
cannot use ? | Result の戻り値がない | Result<(), E> を返す |
アンチパターン
| アンチパターン | 問題点 | より良い方法 |
|---|---|---|
あらゆる場所で .unwrap() を使う | 本番環境でパニックする | .expect("reason") または ? |
| エラーを黙って無視する | バグが隠れる | 処理するか、伝播させる |
想定内のエラーに panic! を使う | UX が悪く、復旧できない | Result |
| あらゆる場所で Box を使う | 型情報が失われる | thiserror |
関連スキル
| 場面 | 参照先 |
|---|---|
| ドメインエラー戦略 | m13-domain-error |
| クレート境界 | m11-ecosystem |
| 型安全なエラー | m05-type-driven |
| メンタルモデル | m14-mental-model |
コメント
Markdown 対応