LoginSignup
192
128

More than 5 years have passed since last update.

RustのOptionとResult

Posted at

はじめに

OptionとResultはRustにおいて非常に重要な型です。
似たような型ですがちゃんと使い分けがあって頻繁に登場するため、これらの取り扱いに慣れておくとrustでのプログラミングが捗ります。
それでは見ていきましょう。

Option<T>型

概要

Option<T>型は 取得できないかもしれない値 を表現する列挙型です。
値が無いことを示すNoneとあることを示すSome(T)のどちらかをとります。
Noneがエラーを表しているわけでは無いことに注意してください。Noneは値がなかったことを示しているだけで、エラーとは限りません。
エラーを表したい場合は後述のResult<T,E>を使う方がふさわしいでしょう。

pub enum Option<T> {
    None,
    Some(T),
}

他の言語でいうnullable typeやHaskellのMaybeと同じ使い方をします。
hashmapから値を取り出す時など、値の有無チェックと値の取得を同時に行いたい場合等に利用します。

fn get_value_bad(v: bool, result: &mut usize) -> bool {
    if v {
        *result = 100;
        true
    } else {
        false
    }
}

fn get_value_good(v: bool) -> Option<usize> {
    if v {
        Some(100)
    } else {
        None
    }
}

fn main() {
    // rustぽくない書き方
    let mut result = 0;
    if get_value_bad(true, &mut result) {
        println!("success: {}", result);
    } else {
        println!("failure");
    }

    // rustぽい書き方
    match get_value_good(true) {
        Some(result) => println!("success: {}", result),
        None => println!("failure"),
    }
}

Optionの便利メソッド

fn main(){
    // Some(v): 値がある場合
    // None: 値が無い場合
    let some_value: Option<usize> = Some(1);
    let none_value: Option<usize> = None;

    // Optionの中身はパターンマッチで取り出す
    match some_value {
        Some(v) => println!("some value = {}", v),
        None => println!("none value"),
    };

    // Someの場合だけ処理したいならif letの方が便利
    if let Some(v) = some_value {
        println!("some value = {}", v);
    }

    // 比較もできる
    // ただし、Option<T>のT型がEqやOrdトレイトを実装している必要がある
    assert!(some_value != none_value);

    // Option<T>には便利メソッドがいろいろある
    // Some(v) or Noneを判定
    assert!(some_value.is_some());
    assert!(none_value.is_none());

    // unwrap: (&self) -> T
    // Someの場合は中身を返し、Noneの場合はpanicして終了
    assert_eq!(some_value.unwrap(), 1);
    // assert_eq!(none_value.unwrap(), 1); // panicする

    // expect: (&self, &str) -> T
    // unwrapと同じだが、panicするときにメッセージを指定できる
    assert_eq!(some_value.expect("panic!"), 1);
    // assert_eq!(none_value.expect("panic!"), 1); // panicする

    // unwrap_or: (&self, T) -> T
    // unwrapと同じだが、Noneの場合にpanicする代わりに別の値を返す
    assert_eq!(some_value.unwrap_or(0), 1);
    assert_eq!(none_value.unwrap_or(0), 0);

    // unwrap_or_else: (&self, FnOnce() -> T) -> T
    // unwrap_orと同じだが、Noneの場合の値をClosureで指定する
    // 遅延評価したいときに使う
    assert_eq!(some_value.unwrap_or_else(|| 0), 1);
    assert_eq!(none_value.unwrap_or_else(|| 0), 0);

    // map: (&self, FnOnce(T) -> U) -> Option<U>
    // Someの場合は関数を適用し、Noneの場合はNoneまま返す
    assert_eq!(some_value.map(|v| format!("value is {}", v)), Some("value is 1".to_string()));
    assert_eq!(none_value.map(|v| format!("value is {}", v)), None);

    // map_or: (&self, U, FnOnce(T) -> U) -> U
    // unwrap_orのmap版
    assert_eq!(some_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string());
    assert_eq!(none_value.map_or("value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string());

    // map_or_else: (&self, FnOnce() -> U, FnOnce(T) -> U) -> U
    // unwrap_or_elseのmap版
    assert_eq!(some_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is 1".to_string());
    assert_eq!(none_value.map_or_else(|| "value is default".to_string(), |v| format!("value is {}", v)), "value is default".to_string());

    // 他にもいろいろあります!
    // https://doc.rust-lang.org/std/option/enum.Option.html
}

Result<T,E>型

概要

Result<T,E>失敗するかもしれない処理の結果を表現する列挙型です。
Resultはrustコンパイラから特別扱いされており、無視するとwarinigが出ます。
Resultは例外がないrustにおける標準のエラーハンドリング方法です。エラーが発生する可能性がある場合は結果にResultを用いるようにしましょう。

enum Result<T, E> {
   Ok(T),
   Err(E),
}

他言語で例外を利用したり、エラーコードを返したいような場合に利用します。
Result型の戻り値を無視するとwarningが出るのでエラーハンドリングしていないことに気づけますし、パターンマッチで取り出せて例外より取り回しやすいのが良いです。

fn get_value_bad(v: bool, result: &mut usize) -> usize {
    if v {
        *result = 100;
        0 
    } else {
        1
    }
}

fn get_value_good(v: bool) -> Result<usize,&'static str> {
    if v {
        Ok(100)
    } else {
        Err("error message")
    }
}

fn main() {
    // エラーコードを返す書き方
    // 利用者が戻り値を無視できるのでよく無い
    let mut result = 0;
    if get_value_bad(true, &mut result) == 0 {
        println!("success: {}", result);
    } else {
        println!("failure");
    }

    // rustぽい書き方
    match get_value_good(true) {
        Ok(result) => println!("success: {}", result),
        Err(msg) => println!("failure: {}", msg),
    }
}

Resultの便利メソッド

fn main(){
    // Ok(v): 処理に成功した場合。返す値が無い場合はUnit型()を返すとよい
    // Err(e): 処理に失敗した場合。
    let ok_value: Result<usize, &'static str> = Ok(1);
    let er_value: Result<usize, &'static str> = Err("error message");

    // Resultの中身はパターンマッチで取り出す
    match ok_value {
        Ok(v) => println!("ok value = {}", v),
        Err(e) => println!("err value = {}", e),
    };

    // どちらか片方の場合だけ処理したいならif letが便利
    if let Ok(v) = ok_value {
        println!("ok value = {}", v);
    }
    if let Err(e) = er_value {
        println!("err value = {}", e);
    }

    // 比較もできる
    // ただし、Reulst<T,E>のTとE両方がEqやOrdトレイトを実装している必要がある
    assert!(ok_value != er_value);

    // Result<T,E>には便利メソッドがいろいろある
    // Ok(v) or Err(e)を判定
    assert!(ok_value.is_ok());
    assert!(er_value.is_err());

    // ok: (&self)
    // Option型に変換する。Okの場合はSomeに、Errの場合はNoneになる
    assert_eq!(ok_value.ok(), Some(1));
    assert_eq!(er_value.ok(), None);

    // err: (&self)
    // okメソッドの逆バージョン。Okの場合はNoneに、Errの場合はSomeになる
    assert_eq!(ok_value.err(), None);
    assert_eq!(er_value.err(), Some("error message"));

    // unwrap<T,E>: (&self) -> T
    // Okの場合は中身を返し、Errの場合はpanicする
    assert_eq!(ok_value.unwrap(), 1);
    // assert_eq!(er_value.unwrap(), 1); // panic

    // expect<T,E>: (&self, &str) -> T
    // unwrapと同じだが、panic時のエラーメッセージを指定できる
    assert_eq!(ok_value.expect("panic"), 1);
    // assert_eq!(er_value.expect("panic"), 1); // panic

    // unwrap_or<T,E>: (&self, T) -> T
    // unwrapと同じだが、panicする代わりに別の値を返す
    assert_eq!(ok_value.unwrap_or(0), 1);
    assert_eq!(er_value.unwrap_or(0), 0);

    // unwrap_or_else<T,E>: (&self, FnOnce(E) -> T) -> T
    // unwrap_orと同じだが、Errの場合の値をClosureで指定する
    // 遅延評価したいときに使う
    // Optionと違ってErrの値を引数に取るので注意
    assert_eq!(ok_value.unwrap_or_else(|_e| 0), 1);
    assert_eq!(er_value.unwrap_or_else(|_e| 0), 0);

    // unwrap_err<T,E>: (&self) -> E
    // expect_err<T,E>: (&self, &str) -> E
    // unwrap, expectのokとerrを逆にしたバージョン。okだとpanicする
    // assert_eq!(ok_value.unwrap_err(), "error message"); // panic
    assert_eq!(er_value.unwrap_err(), "error message");
    // assert_eq!(ok_value.expect_err("panic"), "error message"); // panic
    assert_eq!(er_value.expect_err("panic"), "error message");

    // map<T,E,U>: (&self, FnOnce(T) -> U) -> Result<U,E>
    // map_err<T,E,U>: (&self, FnOnce(E) -> U) -> Result<T,U>
    // Resultの中身を関数を適用してmapする
    // Okの場合に適用するmapとErrの場合に適用するmap_errがある
    assert_eq!(ok_value.map(|v| format!("{}", v)), Ok("1".to_string()));
    assert_eq!(er_value.map(|v| format!("{}", v)), Err("error message"));
    assert_eq!(ok_value.map_err(|s| s.len()), Ok(1));
    assert_eq!(er_value.map_err(|s| s.len()), Err(13));
}

192
128
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
192
128