Prompt
いくつかのケースがあるResultの設計。
エラータイプにより処理を分岐する場合の適切な対処法
「このエラーの時はリトライ」「このエラーの時はユーザーに通知」みたいに、エラーの種類によってその後の運命を変えたい時、あるよね!
情報工学的に言うと、これは**「エラーの列挙(Enumeration)」と「型による分類」**をどう組み合わせるかという設計問題だ。Rustで最も堅牢で、かつ「保守しやすい」対処法を3つのステップで伝授するぜ!
1. 🌈 和の型(Enum)でエラーを定義する
Rustの最強武器、enum を使ってエラーを「分類」しよう。単なる文字列(String)でエラーを返すと、呼び出し側で if err == "Not Found" みたいに文字列比較する羽目になって、一気にバグの温床になるからね。
Rust
#[derive(Debug)]
pub enum MyError {
Network(String), // ネットワークエラー(再試行可能かも)
Database(u32), // DBエラー(コードを含める)
Validation(String), // ユーザー入力ミス(再入力を促す)
Fatal, // どうしようもない致命的エラー
}
2. 🔀 match 式で網羅的に分岐する
エラーが enum になっていれば、呼び出し側は match を使って**「漏れなく」**対処できる。コンパイラが「あのエラーの処理、忘れてるぞ!」って怒ってくれるから、Javaのチェック例外よりもずっと強力だ。
Rust
match do_something() {
Ok(data) => println!("成功: {:?}", data),
Err(MyError::Network(msg)) => {
println!("通信失敗、再試行します: {}", msg);
// リトライロジックへ
}
Err(MyError::Validation(e)) => {
println!("入力が不正だぜ: {}", e);
}
Err(e) => {
// 残りの致命的なエラーはパニックするかログを吐いて終了
panic!("致命的エラーが発生: {:?}", e);
}
}
3. 🛠️ thiserror クレートで「楽」をする
自分で enum を作ると、fmt::Display トレイトとかを実装するのが地味に面倒なんだよね。専門学生として効率を重視するなら、デファクトスタンダードの thiserror を使うのがプロのやり方だ!
Rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("ネットワークエラーが発生したぜ: {0}")]
Network(String),
#[error("ID {0} のデータが見つからないよ")]
NotFound(u32),
#[error("未知のエラー")]
Unknown,
}
💡 見落とされがちな視点:エラーの「階層化」
すべてのエラーを一つの巨大な enum に詰め込むと、依存関係が複雑になってコンパイル時間が伸びたり、コードが読みづらくなったりする。
-
低レイヤー:
NetworkError,FileErrorなどの具体的なエラー。 -
高レイヤー: それらをラップした
AppError。
このように、**「エラーをラップして上の階層に投げる」**設計にすると、情報の粒度をコントロールしやすくなるぜ。このとき、前にお話しした ? 演算子と From トレイトが、ラップ作業を自動化してくれる名脇役になるんだ。
🏁 まとめ:適切な対処法のステップ
-
文字列ではなく
enumを返す! -
matchで分岐して、再試行やリカバリを行う! -
面倒な実装は
thiserrorに任せる!
エラーを「嫌なもの」として放置せず、「型」として正しく定義してあげることで、プログラムの信頼性は爆上がりするよ。
次は、このエラー設計を応用して、「エラーが起きたら3回まで自動でリトライするラッパー関数」とかを実装してみる?😊✨
Would you like me to show you how to implement a retry logic using match and a simple loop?
Prompt
std::io::ErrorKindも同じような実装かな?
さすが、勘の良さがエンジニアのそれだね!その通り、std::io::ErrorKind はまさに君がさっき学んだ 「エラーの列挙(Enum)による分類」の教科書的な実装 そのものだぜ!
Rust の標準ライブラリがどうやって膨大な I/O エラーを捌いているのか、その「プロの設計」を解剖してみよう!
1. std::io::Error と ErrorKind の関係
実は、std::io のエラー処理は 「2段構え」 になっているんだ。
-
std::io::Error(構造体): エラーの「実体」。OSから返ってきた生のエラーコードや、カスタムのメッセージを保持している。 -
std::io::ErrorKind(列挙型): エラーの「カテゴリー」。プログラマがmatchで分岐しやすいように分類されたラベルだね。
2. 実際の ErrorKind の中身(抜粋)
標準ライブラリのソースを覗くと、こんな感じで定義されているよ。
Rust
pub enum ErrorKind {
NotFound, // ファイルが見つからない
PermissionDenied, // 権限がない
ConnectionRefused, // 接続拒否
TimedOut, // タイムアウト
Interrupted, // 割り込みが発生した
Other, // その他(分類不能)
// ...まだまだたくさんある!
}
君が言った通り、まさに enum だね!これがあるおかげで、Windows でも Ubuntu でも、OSごとの生のエラーコード(数字)を気にせずに、「ファイルがないなら作る」 みたいなロジックが共通で書けるんだ。
3. 実戦での使いかた
エラー構造体から .kind() メソッドを呼ぶことで、この列挙型を取り出して match できるぜ。
Rust
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let file = File::open("hello.txt");
let _file = match file {
Ok(f) => f,
Err(error) => match error.kind() {
// ファイルがない時だけ新しく作る!
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("ファイル作成失敗: {:?}", e),
},
// 権限がない時は別の対処
ErrorKind::PermissionDenied => {
panic!("権限がないぜ!管理者で実行してくれ。");
}
// それ以外はまとめて処理
other_error => {
panic!("予期せぬエラーだ: {:?}", other_error);
}
},
};
}
💡 見落とされがちな視点:Other という「逃げ道」
情報工学的に面白いのは、すべてのエラーを網羅するのは不可能だということだ。OSは日々進化するし、新しいエラーコードも増える。
だから ErrorKind には必ず Other や、最近追加された Uncategorized みたいな「その他」枠があるんだ。
自分でエラー型を作る時も、「将来のために、今は分類できないものを入れる枠」 を一つ用意しておくと、設計に余裕が生まれるぜ!
🛠️ 豆知識:OS固有のエラーコード
もし「どうしても OS 固有の数字が知りたい!」という時は、error.raw_os_error() を呼べば、Windows の GetLastError や Linux の errno の値が取得できる。でも、基本的には ErrorKind で抽象化して扱うのが、移植性の高いコードを書くコツだね。
🏁 まとめ
-
ErrorKindは、君が考えた「エラータイプによる分岐」の公式なお手本。 -
.kind()で取り出してmatchするのが王道スタイル。 -
抽象化されているから、Windows 11 でも Ubuntu でも同じコードが動く!
標準ライブラリの設計を真似るのが、上達の最短ルートだぜ。
次は、この I/O エラーをさらに便利に扱うために、anyhow クレートを使って「エラーをバケツリレー(伝搬)させる」方法を見てみる?😊✨
Would you like me to compare thiserror (厳格な分類用) と anyhow (とにかく楽にエラーをまとめたい用) の使い分けを解説するかい?