あっぽログ
← 記事一覧に戻る

RustのエラーハンドリングをResult型とQuestion Mark演算子で理解する

Rustのエラーハンドリングとは

多くのプログラミング言語では、エラーが発生したときに例外(Exception)をスローして処理します。しかしRustには例外機構がありません。代わりに、エラーを値として扱うという設計思想が採用されています。

Rustのエラーハンドリングの中心となるのが以下の2つの型です。

  • Result<T, E> … 成功か失敗かを表す型
  • Option<T> … 値があるかないかを表す型

この仕組みにより、コンパイラがエラーの取り扱い漏れを検出してくれるため、実行時に予期せずクラッシュするリスクを大幅に減らせます。


Result型の基本

Result<T, E> は以下の2つのバリアントを持つ列挙型です。

enum Result<T, E> {
    Ok(T),  // 成功。値Tを持つ
    Err(E), // 失敗。エラーEを持つ
}

ファイル読み込みの例

use std::fs;

fn read_file(path: &str) -> Result<String, std::io::Error> {
    let contents = fs::read_to_string(path)?;
    Ok(contents)
}

fn main() {
    match read_file("hello.txt") {
        Ok(contents) => println!("ファイルの中身:\n{}", contents),
        Err(e) => eprintln!("エラーが発生しました: {}", e),
    }
}

match を使って OkErr の両方を明示的に処理しているのがポイントです。コンパイラはどちらかのケースを書き忘れると警告を出してくれます。


Question Mark演算子(?)の便利な使い方

エラー処理を毎回 match で書くのは冗長になりがちです。そこで役立つのが ?演算子 です。

?ResultErr だった場合に即座にその関数からリターンし、Ok だった場合はその中身を取り出してくれます。

?演算子を使った書き直し

use std::fs;
use std::io;

fn read_username(path: &str) -> Result<String, io::Error> {
    // ?を使うことでmatchを省略できる
    let username = fs::read_to_string(path)?;
    Ok(username.trim().to_string())
}

fn main() {
    match read_username("username.txt") {
        Ok(name) => println!("ユーザー名: {}", name),
        Err(e) => eprintln!("読み込み失敗: {}", e),
    }
}

? を連鎖させることで、複数の処理をすっきりと書けます。

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let n: i32 = s.trim().parse()?; // パース失敗ならErrを返す
    Ok(n * 2)
}

fn main() {
    println!("{:?}", parse_and_double("21"));  // Ok(42)
    println!("{:?}", parse_and_double("abc")); // Err(...)
}

Option型との違い

Option<T> はエラーではなく「値が存在しないこと」を表します。

fn find_first_even(numbers: &[i32]) -> Option<i32> {
    for &n in numbers {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None // 見つからなかった
}

fn main() {
    let nums = vec![1, 3, 5, 4, 7];
    match find_first_even(&nums) {
        Some(n) => println!("最初の偶数: {}", n),
        None => println!("偶数は見つかりませんでした"),
    }
}

Option? 演算子が使えます(Option を返す関数の中で使用)。

用途
Result<T, E>処理が成功/失敗する可能性があるとき
Option<T>値が存在する/しない可能性があるとき

unwrapとexpectは慎重に

unwrap()expect() を使うと ResultOption から手軽に値を取り出せますが、ErrNone のときに**パニック(プログラムの強制終了)**が発生します。

// 危険な例:ファイルがなければパニックする
let contents = fs::read_to_string("config.txt").unwrap();

// expectはパニック時のメッセージを指定できる(デバッグには有用)
let contents = fs::read_to_string("config.txt")
    .expect("config.txtが見つかりません");

unwrap / expect はプロトタイプ作成時やテストコードでは便利ですが、本番コードでは match? を使ったきちんとしたエラーハンドリングを心がけましょう


まとめ

Rustのエラーハンドリングをまとめると次のようになります。

  • Result<T, E> でエラーを値として表現する
  • match で成功・失敗の両ケースを漏れなく処理する
  • ?演算子 でエラーを伝播させてコードをすっきりさせる
  • Option<T> は「値なし」を表し、Result と使い分ける
  • unwrap / expect はデバッグ用途に留める

Rustのエラーハンドリングは最初は難しく感じるかもしれませんが、慣れると「エラーを見落とさない安心感」が得られます。ぜひ小さなプログラムから実践してみてください!

← 記事一覧に戻る