Rustのエラーハンドリング入門:Result・Option・?演算子の使い方

スポンサーリンク

Rustのエラーハンドリング入門:Result・Option・?演算子の使い方

はじめに

Rustには例外(Exception)がありません。代わりに Result<T, E>Option<T> という型でエラーや値の有無を表現します。コンパイラがエラーの見落としを防いでくれるため、実行時の予期しないクラッシュを大幅に減らせます。


Option:値があるかないか

Option<T> は値が存在するかどうかを表します。

enum Option<T> {
    Some(T),  // 値がある
    None,     // 値がない
}

Vec::getHashMap::get など、値が見つからない可能性があるメソッドが Option を返します。

let v = vec![1, 2, 3];

let first = v.get(0);   // Some(&1)
let tenth = v.get(9);   // None

Option の扱い方

let v = vec![1, 2, 3];
let val = v.get(0);

// match で分岐
match val {
    Some(x) => println!("値: {}", x),
    None => println!("見つかりません"),
}

// if let(None を無視したい場合)
if let Some(x) = val {
    println!("値: {}", x);
}

// unwrap(None ならパニック。テスト用途向き)
let x = val.unwrap();

// unwrap_or(None のときのデフォルト値)
let x = val.unwrap_or(&0);

// unwrap_or_else(None のときに関数を実行)
let x = val.unwrap_or_else(|| &99);

// ?演算子(None なら早期リターン)
fn first_element(v: &Vec<i32>) -> Option<&i32> {
    let x = v.get(0)?;  // None なら関数を抜ける
    Some(x)
}

Option のメソッド

let some: Option<i32> = Some(5);
let none: Option<i32> = None;

some.is_some()           // true
some.is_none()           // false
some.map(|x| x * 2)     // Some(10)
none.map(|x| x * 2)     // None
some.filter(|x| *x > 3) // Some(5)
some.filter(|x| *x > 9) // None
some.or(Some(99))        // Some(5)
none.or(Some(99))        // Some(99)
some.unwrap_or(0)        // 5
none.unwrap_or(0)        // 0

Result<T, E>:成功か失敗か

Result<T, E> は処理が成功したか失敗したかを表します。

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

ファイル操作やパースなど、失敗する可能性のある処理が Result を返します。

use std::fs;

let result = fs::read_to_string("hello.txt");
// Ok(String) または Err(io::Error)

Result の扱い方

use std::fs;

let result = fs::read_to_string("hello.txt");

// match で分岐
match result {
    Ok(content) => println!("{}", content),
    Err(e) => eprintln!("エラー: {}", e),
}

// if let
if let Ok(content) = fs::read_to_string("hello.txt") {
    println!("{}", content);
}

// unwrap(Err ならパニック)
let content = fs::read_to_string("hello.txt").unwrap();

// expect(パニック時のメッセージを指定)
let content = fs::read_to_string("hello.txt")
    .expect("ファイルが読み込めません");

// unwrap_or(Err のときのデフォルト値)
let content = fs::read_to_string("hello.txt")
    .unwrap_or_else(|_| String::from("デフォルト"));

Result のメソッド

let ok: Result<i32, &str> = Ok(5);
let err: Result<i32, &str> = Err("失敗");

ok.is_ok()               // true
ok.is_err()              // false
ok.map(|x| x * 2)        // Ok(10)
err.map(|x| x * 2)       // Err("失敗")
ok.map_err(|e| format!("エラー: {}", e))   // Ok(5)
err.map_err(|e| format!("エラー: {}", e))  // Err("エラー: 失敗")
ok.unwrap_or(0)           // 5
err.unwrap_or(0)          // 0
ok.ok()                   // Some(5)(Result → Option に変換)

? 演算子:エラーを早期リターン

関数内で ? を使うと、Err のとき自動で return Err(...) します。エラーを伝播させるコードがすっきり書けます。

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

// ? なし(冗長)
fn read_file_verbose(path: &str) -> Result<String, io::Error> {
    let content = match fs::read_to_string(path) {
        Ok(c) => c,
        Err(e) => return Err(e),
    };
    Ok(content)
}

// ? あり(すっきり)
fn read_file(path: &str) -> Result<String, io::Error> {
    let content = fs::read_to_string(path)?;
    Ok(content)
}

複数のエラーを ? で繋げる

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

fn read_and_parse(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;  // io::Error かもしれない
    let number: i32 = content.trim().parse()?; // ParseIntError かもしれない
    Ok(number)
}

Box<dyn std::error::Error> を使うと異なる型のエラーをまとめて扱えます。


unwrap・expect・? の使い分け

方法 使う場面
unwrap() テストコード・プロトタイプ(失敗しないと確信できる場合)
expect("msg") 失敗が設計上あり得ない場所(メッセージで原因を明示)
? 演算子 エラーを呼び出し元に伝播したい場合(本番コードの基本)
match / if let エラーごとに異なる処理をしたい場合
unwrap_or デフォルト値で代替できる場合

main関数でのエラーハンドリング

mainResult を返せます。? を使ったエラー処理がそのまま書けます。

use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = fs::read_to_string("hello.txt")?;
    println!("{}", content);
    Ok(())
}

エラーが発生すると標準エラー出力にメッセージが表示されます。


まとめ

使い方
Option<T> 値があるかないか(null の代わり)
Result<T, E> 成功か失敗か(例外の代わり)
Some(x) / None Option の2つの状態
Ok(x) / Err(e) Result の2つの状態
unwrap() 値を取り出す(失敗時パニック)
unwrap_or(default) 失敗時はデフォルト値
? 演算子 エラーを呼び出し元に伝播
map(f) 値を変換する(エラーはそのまま)

Rustのファイル読み書きで ? を実際に使う例は「Rustのファイル読み書き入門:fs・BufReader・BufWriterの使い方」を参照してください。

Rustのイテレータについては「Rustのイテレータ入門:map・filter・collectの使い方」を参照してください。