21
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Rustを書いてると避けて通れないのが Result<T, E>Option<T>

最初は「両方とも『値があるかないか』を表すんでしょ?何が違うの?」って思ってました。

全然違った。

この記事では、この2つの使い分けを整理します。

目次

  1. それぞれの定義
  2. 使い分けの基準
  3. よく使うメソッド
  4. 変換メソッド
  5. ?演算子
  6. 実践的なパターン
  7. まとめ

それぞれの定義

Option

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

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

Result

enum Result<T, E> {
    Ok(T),    // 成功
    Err(E),   // 失敗(エラー情報付き)
}

「成功したか、失敗したか(失敗なら理由付き)」 を表す。

使い分けの基準

Option を使う場面

「ないこと」が正常なケースで、エラーではない

fn find_user(id: u32) -> Option<User> {
    // ユーザーが見つからないのは「エラー」ではない
    // 単に「いない」だけ
}

fn first_element<T>(vec: &[T]) -> Option<&T> {
    // 空のベクタに「最初の要素」はない
    // でもそれは正常な状態
    vec.first()
}

Optionが適切な例

  • コレクションの検索(見つからないかもしれない)
  • パースの結果(値がないかもしれない)
  • デフォルト値があるかどうか
  • オプショナルな設定項目

Result を使う場面

「失敗」であり、その理由を伝えたい

fn read_file(path: &str) -> Result<String, std::io::Error> {
    // ファイルが読めないのは「エラー」
    // 理由(パーミッション、存在しない等)を伝えたい
    std::fs::read_to_string(path)
}

fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
    // パースに失敗したら「エラー」
    // 何が悪かったのか知りたい
    s.parse()
}

Resultが適切な例

  • ファイル操作(I/Oエラーの可能性)
  • ネットワーク通信(接続エラーの可能性)
  • パース処理(不正な入力の可能性)
  • データベース操作(クエリエラーの可能性)

迷ったときのフローチャート

「値がない」状態は...
    │
    ├─ 正常な状態である → Option
    │   例:空のリストの最初の要素
    │
    └─ 異常な状態(エラー)である → Result
        │
        └─ エラーの理由を伝えたいか?
            ├─ Yes → Result<T, カスタムError>
            └─ No → Result<T, ()> か Option

よく使うメソッド

Option のメソッド

unwrap系

let x: Option<i32> = Some(5);

x.unwrap();           // Some → 値、None → パニック
x.unwrap_or(0);       // Some → 値、None → デフォルト値
x.unwrap_or_else(|| compute_default());  // None時に関数実行
x.unwrap_or_default();  // None → T::default()
x.expect("エラーメッセージ");  // Noneでパニック(メッセージ付き)

変換系

let x: Option<i32> = Some(5);

x.map(|n| n * 2);           // Some(5) → Some(10)
x.and_then(|n| Some(n * 2));  // flatMap的な
x.filter(|n| *n > 3);       // 条件を満たさないとNone

判定系

let x: Option<i32> = Some(5);

x.is_some();  // true
x.is_none();  // false

Result のメソッド

unwrap系

let x: Result<i32, &str> = Ok(5);

x.unwrap();           // Ok → 値、Err → パニック
x.unwrap_or(0);       // Ok → 値、Err → デフォルト値
x.unwrap_err();       // Err → エラー値、Ok → パニック
x.expect("失敗した理由");  // Errでパニック(メッセージ付き)

変換系

let x: Result<i32, &str> = Ok(5);

x.map(|n| n * 2);           // Ok(5) → Ok(10)
x.map_err(|e| format!("Error: {}", e));  // エラーを変換
x.and_then(|n| Ok(n * 2));  // flatMap的な

判定系

let x: Result<i32, &str> = Ok(5);

x.is_ok();   // true
x.is_err();  // false

変換メソッド

Option → Result

let opt: Option<i32> = Some(5);

// Noneの場合のエラー値を指定
let result: Result<i32, &str> = opt.ok_or("値がない");

// 遅延評価版
let result: Result<i32, String> = opt.ok_or_else(|| format!("値がない"));

Result → Option

let result: Result<i32, &str> = Ok(5);

// Errを捨ててOptionに
let opt: Option<i32> = result.ok();

// Okを捨ててエラーをOptionに
let err_opt: Option<&str> = result.err();

使用例

fn find_and_process(id: u32) -> Result<String, MyError> {
    // find_userはOption<User>を返す
    // ok_orでResult<User, MyError>に変換
    let user = find_user(id)
        .ok_or(MyError::UserNotFound(id))?;
    
    Ok(process(user))
}

?演算子

?演算子は、エラーハンドリングを楽にする神機能。

Result での使い方

fn read_config() -> Result<Config, std::io::Error> {
    // ?をつけると、Errの場合は早期リターン
    let content = std::fs::read_to_string("config.toml")?;
    let config = parse_config(&content)?;
    Ok(config)
}

// 上は以下と同じ
fn read_config_verbose() -> Result<Config, std::io::Error> {
    let content = match std::fs::read_to_string("config.toml") {
        Ok(c) => c,
        Err(e) => return Err(e),
    };
    let config = match parse_config(&content) {
        Ok(c) => c,
        Err(e) => return Err(e),
    };
    Ok(config)
}

Option での使い方

fn get_nested_value(data: &Data) -> Option<i32> {
    let a = data.get_a()?;  // Noneなら早期リターン
    let b = a.get_b()?;
    let c = b.get_c()?;
    Some(c.value)
}

エラー型の変換

?を使うとき、エラー型が異なると変換が必要:

use std::num::ParseIntError;
use std::io;

#[derive(Debug)]
enum MyError {
    Io(io::Error),
    Parse(ParseIntError),
}

impl From<io::Error> for MyError {
    fn from(e: io::Error) -> Self {
        MyError::Io(e)
    }
}

impl From<ParseIntError> for MyError {
    fn from(e: ParseIntError) -> Self {
        MyError::Parse(e)
    }
}

fn process() -> Result<i32, MyError> {
    let content = std::fs::read_to_string("number.txt")?;  // io::Error → MyError
    let num: i32 = content.trim().parse()?;  // ParseIntError → MyError
    Ok(num)
}

実践的なパターン

パターン1:連鎖的な処理

fn get_user_email(user_id: u32) -> Option<String> {
    find_user(user_id)
        .and_then(|user| user.email)
        .map(|email| email.to_lowercase())
}

パターン2:デフォルト値の処理

fn get_config_value(key: &str) -> String {
    config.get(key)
        .cloned()
        .unwrap_or_else(|| default_config(key))
}

パターン3:複数のOptionを組み合わせ

fn calculate(a: Option<i32>, b: Option<i32>) -> Option<i32> {
    Some(a? + b?)  // どちらかがNoneならNone
}

// または
fn calculate_alt(a: Option<i32>, b: Option<i32>) -> Option<i32> {
    match (a, b) {
        (Some(x), Some(y)) => Some(x + y),
        _ => None,
    }
}

パターン4:ログを出しつつ処理

fn process_with_logging(data: Option<Data>) -> Option<Result<Output, Error>> {
    data.map(|d| {
        println!("Processing: {:?}", d);
        process(d)
    })
}

パターン5:イテレータとの組み合わせ

fn sum_valid_numbers(inputs: &[&str]) -> i32 {
    inputs.iter()
        .filter_map(|s| s.parse::<i32>().ok())  // パース成功したものだけ
        .sum()
}

よくある間違い

❌ unwrap の乱用

// 悪い例
fn get_data() -> Data {
    fetch_data().unwrap()  // パニックする可能性
}

// 良い例
fn get_data() -> Result<Data, Error> {
    fetch_data()  // エラーを呼び出し元に伝播
}

❌ Option を Result として使う

// 悪い例:エラーの理由がわからない
fn parse_config(s: &str) -> Option<Config> {
    // パースに失敗した理由を伝えられない
}

// 良い例
fn parse_config(s: &str) -> Result<Config, ParseError> {
    // エラーの詳細を返せる
}

❌ 無駄な match

// 悪い例
let value = match opt {
    Some(v) => v,
    None => default,
};

// 良い例
let value = opt.unwrap_or(default);

まとめ

使い分け早見表

状況 使うべき型
値がないのが正常 Option<T>
値がないのがエラー Result<T, E>
エラーの理由を伝えたい Result<T, E>
検索で見つからない Option<T>
ファイルI/O Result<T, io::Error>
パース処理 Result<T, ParseError>

チェックリスト

  • unwrap() を使う前に本当に安全か考える
  • エラーの理由を伝えるべきなら Result を使う
  • ?演算子を活用してコードを簡潔に
  • map, and_then, unwrap_or を使いこなす

今すぐできるアクション

  1. 既存コードの unwrap() を見直す
  2. OptionResult の変換メソッドを覚える
  3. ?演算子を使ってエラーハンドリングを簡潔にする

ResultOption、最初は混乱するけど、「エラーかどうか」で考えると使い分けられるようになります。

みなさんも良いRustライフを!

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

21
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?