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にする