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 を使って Ok と Err の両方を明示的に処理しているのがポイントです。コンパイラはどちらかのケースを書き忘れると警告を出してくれます。
Question Mark演算子(?)の便利な使い方
エラー処理を毎回 match で書くのは冗長になりがちです。そこで役立つのが ?演算子 です。
? は Result が Err だった場合に即座にその関数からリターンし、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() を使うと Result や Option から手軽に値を取り出せますが、Err や None のときに**パニック(プログラムの強制終了)**が発生します。
// 危険な例:ファイルがなければパニックする
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のエラーハンドリングは最初は難しく感じるかもしれませんが、慣れると「エラーを見落とさない安心感」が得られます。ぜひ小さなプログラムから実践してみてください!