Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

エラーハンドリング入門

プログラムで起こりうるエラーを適切に処理する方法を学びます。

Rustのエラー処理の考え方

多くの言語では「例外」を使いますが、Rustは型システムでエラーを扱います。

他の言語:  成功 or 例外(突然プログラムが止まる)
Rust:     Result<成功, 失敗> を返す(呼び出し側が処理を決める)

これにより、エラーを「忘れて無視する」ことが難しくなります。

Option型:値があるかないか

Optionは「値があるかもしれないし、ないかもしれない」を表します。

Option の定義(イメージ)

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),  // 値がある
    None,     // 値がない
}
}

使用例

fn main() {
    let numbers = vec![1, 2, 3];

    // インデックス1の要素を取得
    match numbers.get(1) {
        Some(n) => println!("見つかった: {}", n),
        None => println!("見つからない"),
    }

    // インデックス10の要素を取得(範囲外)
    match numbers.get(10) {
        Some(n) => println!("見つかった: {}", n),
        None => println!("見つからない"),
    }
}

出力:

見つかった: 2
見つからない

なぜ Option を使うのか

#![allow(unused)]
fn main() {
// 他の言語でよくあるパターン(危険)
// let value = array[10];  // 範囲外 → クラッシュ!

// Rustのパターン(安全)
let value = numbers.get(10);  // Option<&i32> を返す
// 必ず Some/None を処理する必要がある
}

Option の便利なメソッド

fn main() {
    let some_number = Some(5);
    let no_number: Option<i32> = None;

    // unwrap_or: Noneの場合のデフォルト値
    println!("{}", some_number.unwrap_or(0));  // 5
    println!("{}", no_number.unwrap_or(0));    // 0

    // is_some / is_none
    println!("{}", some_number.is_some());  // true
    println!("{}", no_number.is_none());    // true
}

Result型:成功か失敗か

Resultは「処理が成功したか失敗したか」を表します。

Result の定義(イメージ)

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),   // 成功(Tは成功時の値)
    Err(E),  // 失敗(Eはエラー情報)
}
}

使用例

fn main() {
    let result = divide(10, 2);
    match result {
        Ok(value) => println!("結果: {}", value),
        Err(e) => println!("エラー: {}", e),
    }

    let result = divide(10, 0);
    match result {
        Ok(value) => println!("結果: {}", value),
        Err(e) => println!("エラー: {}", e),
    }
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("ゼロで割ることはできません"))
    } else {
        Ok(a / b)
    }
}

出力:

結果: 5
エラー: ゼロで割ることはできません

ファイル操作の例

use std::fs;

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

    match result {
        Ok(content) => println!("内容: {}", content),
        Err(e) => println!("読み込みエラー: {}", e),
    }
}

ファイルが存在しない場合、Errが返されます。

unwrapとexpect(注意が必要)

unwrap

SomeまたはOkの値を取り出します。NoneErrの場合はパニック(プログラム停止)。

fn main() {
    let some_value = Some(5);
    let value = some_value.unwrap();  // 5
    println!("{}", value);

    // let none_value: Option<i32> = None;
    // let value = none_value.unwrap();  // パニック!
}

expect

unwrapと同じですが、エラーメッセージを指定できます。

fn main() {
    let result: Result<i32, &str> = Err("何か問題が発生");
    // let value = result.expect("致命的なエラー");  // パニック!メッセージ付き
}

unwrap/expectを使っていい場面

  • 学習中・プロトタイプ: 素早く動かしたいとき
  • 絶対に失敗しない場合: 論理的に失敗しないことが保証されるとき
#![allow(unused)]
fn main() {
// OK: "42"は必ず数値に変換できる
let number: i32 = "42".parse().unwrap();
}

本番コードでは避ける

#![allow(unused)]
fn main() {
// 避ける
let content = fs::read_to_string("config.txt").unwrap();

// 推奨
let content = fs::read_to_string("config.txt")
    .expect("設定ファイルの読み込みに失敗");  // せめてメッセージを

// より良い: エラーを伝播
fn read_config() -> Result<String, std::io::Error> {
    fs::read_to_string("config.txt")
}
}

if letによる簡潔な処理

特定のパターンだけ処理したい場合に便利です。

fn main() {
    let some_value = Some(5);

    // matchを使う場合
    match some_value {
        Some(n) => println!("値: {}", n),
        None => {}  // 何もしない
    }

    // if letを使う場合(簡潔)
    if let Some(n) = some_value {
        println!("値: {}", n);
    }
}

まとめ

用途バリアント
Option<T>値の有無Some(T), None
Result<T, E>成功/失敗Ok(T), Err(E)
メソッド動作注意
unwrap()値を取り出す失敗でパニック
expect("msg")値を取り出す失敗でメッセージ付きパニック
unwrap_or(default)値またはデフォルト安全
is_some() / is_ok()成功かチェック安全

確認テスト

Q1. Option<T>Noneは何を表す?

Q2. unwrap()を使う際のリスクは?

Q3. 以下のコードの出力は?
let numbers = vec![10, 20, 30]; let value = numbers.get(5).unwrap_or(&0); println!("{}", value);

Q4. unwrap()を使わずに安全にOptionを処理する方法として適切でないのは?

Q5. safe_divide(10, 0)がNoneを返す関数を実装する場合、正しいのは?


Phase 1 完了!

おめでとうございます!Phase 1を完了しました。

学んだこと:

  • 変数と型(let、mut、基本型)
  • 演算子(算術、比較、論理)
  • 制御フロー(if、loop、while、for、match)
  • 関数(引数、戻り値、式と文)
  • コレクション(配列、タプル、Vec)
  • エラーハンドリング入門(Option、Result)

次のPhase: Phase 2: 所有権とメモリ

Phase 2はRust最重要の概念「所有権」を学びます。ここが理解できればRustマスターへの道が開けます!