Rustにおけるエラー処理とpanicの捕捉

1. 概要

Rustでは、他の多くのプログラミング言語と異なり、エラー処理が言語設計の中心に位置づけられています。一般的に例外と呼ばれるものはRustでは「エラー」と呼ばれ、回復可能なエラーと回復不能なエラー(致命的なエラー)に分類されます。

回復不能なエラーはpanic!マクロによって生成され、これはスタックの巻き戻し(unwinding)やプログラムの終了を引き起こします。他の言語ではtry-catchで処理されるような状況でも、Rustではpanicが発生する場合があります。

2. panic!マクロによるエラー処理

panic!が実行されると、Rustはデフォルトでスタックを巻き戻しながらエラーメッセージを出力し、最終的にプログラムを終了します。この挙動は環境変数RUST_BACKTRACEを設定することで、バックトレースの出力が可能になります。

以下の2種類の終了方法があります:

  • abort: 即座に終了し、OSにリソースの解放を任せます。
  • unwind: スタックを巻き戻しながら各関数のローカル変数を解放します。

Releaseビルドでpanic = 'abort'を設定する例:

[profile.release]
panic = 'abort'

開発中はdevプロファイルで同様の設定が可能です。

3. Resultと回復可能なエラー

Result<T, E>は列挙型で、処理の成功(Ok)または失敗(Err)を表します。これは他の言語の例外と似ていますが、明示的にエラーを処理する必要があります。

3.1 ファイル操作の例

以下のコードはファイルを開き、存在しない場合は新規作成します:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("ファイル作成中に問題が発生しました: {:?}", error);
            })
        } else {
            panic!("ファイルを開く際に問題が発生しました: {:?}", error);
        }
    });
}

3.2 unwrapとexpect

unwrap()Okなら値を返し、Errならpanicを発生させます。

expect()unwrap()に加えて、panic時に任意のメッセージを指定できます:

let greeting_file = File::open("hello.txt")
    .expect("hello.txtはプロジェクトに含まれている必要があります");

3.3 エラーの伝搬と?

関数内でエラーを伝搬させるには、?演算子が簡潔で効果的です:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();
    File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

3.4 main関数での?

main関数でも?を使えるようにするには、戻り値の型をResultにします:

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let _file = File::open("hello.txt")?;
    Ok(())
}

4. panicの捕捉

panicはデフォルトでスタックを巻き戻すため、catch_unwindで捕捉可能です:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("これはpanicです");
    });

    match result {
        Ok(_) => println!("処理が成功しました"),
        Err(_) => println!("panicが捕捉されました"),
    }
}

4.1 捕捉できないpanic

ただし、生ポインタの不正使用などにより発生するpanicは捕捉できません:

fn unsafe_pointer_example() {
    let raw = Box::into_raw(Box::new(42));
    unsafe {
        // メモリ解放後、無効なポインタを参照
        drop(Box::from_raw(raw));
        println!("{}", *raw); // 未定義動作、panicが捕捉できない
    }
}

5. まとめ

Rustのエラー処理は、安全性と表現力のバランスを取る設計となっています:

  • 回復可能なエラーはResultで明示的に処理する
  • 致命的なエラーはpanic!で表現され、捕捉可能な場合と不可能な場合がある
  • ?演算子により、エラー伝搬が簡潔に書ける
  • main関数で?を使うには戻り値をResultにする

タグ: rust Error Handling panic

6月26日 20:32 投稿