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 トレイトが、ラップ作業を自動化してくれる名脇役になるんだ。


🏁 まとめ:適切な対処法のステップ

  1. 文字列ではなく enum を返す!

  2. match で分岐して、再試行やリカバリを行う!

  3. 面倒な実装は 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::ErrorErrorKind の関係

実は、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 (とにかく楽にエラーをまとめたい用) の使い分けを解説するかい?