⚠️

Resultとエラー処理 — begin/rescueが消えた場所

例外の代わりに戻り値でエラーを処理するRustの方式

Rubyのエラー処理は例外ベースだ。何かがおかしくなればraiseし、どこかでrescueで捕まえる。捕まえなければプログラムが死ぬ。

# Ruby
def read_config
  File.read("config.toml")
rescue Errno::ENOENT => e
  puts "ファイルなし: #{e.message}"
  nil
end

Rustには例外がない。エラーが発生しうる関数はResult<T, E>を返す。

// Rust
fn read_config() -> Result<String, std::io::Error> {
    std::fs::read_to_string("config.toml")
}

Resultの使い方

match read_config() {
    Ok(content) => println!("{}", content),
    Err(e) => println!("ファイルなし: {}", e),
}

Optionと同様にmatchで分岐する。Ok(T)は成功、Err(E)は失敗。

?演算子 — Rubyの例外伝播に対応

Rubyではrescueしなければ例外が上位に伝播する。Rustでは?を付けるとエラーが上位関数に伝播する。

# Ruby — 自動伝播
def load_app
  config = File.read("config.toml")  # 失敗すると例外が上がる
  parse(config)
end

// Rust — ?で明示的伝播
fn load_app() -> Result<App, Box<dyn std::error::Error>> {
    let config = std::fs::read_to_string("config.toml")?;  // Errならここで返却
    let app = parse(&config)?;
    Ok(app)
}

?は「Errなら即座に返却、Okなら値を取り出して続行」だ。Rubyの自動伝播を明示的にしたもの。

unwrapとexpect

let config = read_config().unwrap();          // Errならpanic
let config = read_config().expect("設定ファイル必須"); // メッセージ付きpanic

プロトタイピングではunwrapを使い、後で適切なエラー処理に置き換えるパターンが一般的。Rubyのrescue => e; raise e的な一時処理に近い。

map_err — エラー変換

fn load_config() -> Result<Config, AppError> {
    let content = std::fs::read_to_string("config.toml")
        .map_err(|e| AppError::Io(e))?;  // io::Error → AppErrorに変換
    parse_config(&content)
        .map_err(|e| AppError::Parse(e))?;
    Ok(config)
}

Rubyのrescue IOError => e; raise AppError, e.messageパターンに対応。

Rubyとの決定的な違い

エラーの可能性が型に現れる。Result<T, E>を返す関数は「この関数は失敗する可能性がある」とシグネチャに明示する。Rubyではどのメソッドが例外を投げるかドキュメントを読まないとわからない。

キーポイント

1

Result<T, E>はOk(値)またはErr(エラー) — 例外の型安全な代替

2

?演算子でエラー伝播 — Rubyの自動例外伝播を明示的に

3

unwrapはプロトタイピング用 — 本番ではmatch/?を使用

4

エラーの可能性が関数シグネチャに現れる

メリット

  • どの関数が失敗しうるか型だけでわかる
  • rescueを忘れて本番で死ぬことがない

デメリット

  • エラー型の定義が面倒 — thiserror/anyhow crateで解決
  • ?を乱用するとエラーの発生箇所の追跡が難しくなることがある

ユースケース

ファイルI/O、ネットワーク要求等、失敗しうる全ての操作 JSON/TOMLパース — 不正な形式の処理