【Rust】Optionの基本と便利なメソッドの使い方

RustにはOptionやResultのように他の言語ではあまり見ない独特な型があります。

最近Rust学習を始めた身ではありますが、OptionやResultといった型はRustプログラミングにおいて重要で、使いこなせるかどうかがRustを使用したコーディングのキーポイントとなるとも思います。

Rustは公式ドキュメントが豊富で、公式ドキュメントを読めばきちんと理解できるようになっていますが、現状は少し敷居が高い(読むのが疲れる)と感じます。

そこで自身の勉強のアウトプット・整理と同時に、同じようにRustを勉強している方や「公式ドキュメントはちょっと、、」というような方が気楽に見れるようにサンプルコードをメインに簡単にまとめてみました。

今回はOption型について見ていきましょう。

この記事でわかること
  • Option型の基本
  • Option型の便利なメソッドとその使い方
目次

基本

Option<T>は次のいずれかの値を示す型です。

  • Some(T):なんらかの値を含むもの
  • None  :値を含まないもの(Nullではない)

これだとわかりにくいかもしれませんが、Option<T>自体の実装はとてもシンプルです。

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

要するにただの列挙型(Enum)です。
値を含む場合はSome(T)、値がない場合はNoneというような単純な型となっています。

NoneNullとは別物というのもNoneはOption列挙型の一部であるに過ぎず、Nullとは違うことは一目瞭然です。

使い方は以下のようになります。

fn sample() {
    let some: Option<i32> = Some(1);
    println!("{:?}", some);
    // -> Some(1)

    let none: Option<i32> = None;
    println!("{:?}", none);
    // -> None
}

便利なメソッド

公式ドキュメントからOptionで特に使いそうなメソッドをリストアップしてみました。

サンプルコードと注意点も簡単に記載しています。

参考リンク

unwrap

Some(T)の中身を取得する。Noneの場合はpanicを引き起こす。

所有権の移動が発生するので同じものに対して複数回は行えない。

fn sample() {
    let some: Option<String> = Some("dummy".to_string());
    println!("{}", some.unwrap()); // -> "dummy"
    println!("{}", some.unwrap()); // 上で所有権が移動済なのでコンパイルエラーとなる

    let none: Option<String> = None;
    println!("{}", none.unwrap()); // Noneの場合はpanicとなる
}

公式ドキュメントでは基本的に使用は推奨されておらず、Noneケースを処理できるような方法が望ましいとされている。(unwrap_orなどを使うなど)

unwrap_or

unwrapの派生形。

Noneに対して実行した場合は引数に指定した値が返却される。

fn sample() {
    let some: Option<String> = Some("dummy".to_string());
    println!("{}", some.unwrap_or("or value".to_string()));
    // -> "dummy"

    println!("{}", some.unwrap_or("or value".to_string()));
    // -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる

    let none: Option<String> = None;
    println!("{}", none.unwrap_or("or value".to_string()));
    // -> "or value"
}

unwrap_or_else

unwrap_orと同様だが、引数には関数を渡す。

Noneの場合に引数に渡した関数が実行されその結果が返却される。

fn sample() {
    let some: Option<String> = Some("dummy".to_string());
    println!("{}", some.unwrap_or_else(|| "or value".to_string()));
    // -> "dummy"

    println!("{}", some.unwrap_or_else(|| "or value".to_string()));
    // -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる

    let none: Option<String> = None;
    println!("{}", none.unwrap_or_else(|| "or value".to_string()));
    // -> "or value"
}

unwrap_or_default

unwrap_orと同様だが、引数指定なしで使用可能。

Noneの場合にOption<T>におけるT型のデフォルト値を返すことができる。

fn sample() {
    let some: Option<String> = Some("dummy".to_string());
    println!("{}", some.unwrap_or_default());
    // -> "dummy"

    println!("{}", some.unwrap_or_default());
    // -> unwrapと同様に所有権の移動が発生するためコンパイルエラーとなる

    let none: Option<String> = None;
    println!("{}", none.unwrap_or_default());
    // -> "" (Stringのデフォルト値)
}

expect

Some(T)の中身を取得する。Noneの場合は引数に指定した文字列でpanicを発生。

所有権の移動が発生するので同じ物に対して複数回は行えない。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.expect("no error"));
    // -> "dummy"

    println!("{:?}", some.expect("compile error"));
    // 上で所有権が移動済なのでコンパイルエラーとなる

    let none: Option<String> = None;
    println!("{:?}", none.expect("panic!!!")); // Noneの場合はpanicとなる
}

as_ref

Option<T>Option<&T>にして参照する。

unwrapmapなどのメソッドはOption<T>の中身を消費(所有権を移動)するメソッドだが、as_refを使用して参照を挟むことで消費しないようにすることができる。

fn sample() {
    let some: Option<String> = Some("dummy".to_string());
    println!("{:?}", some.unwrap()); // unwrapで所有権が移動する
    println!("{:?}", some.unwrap()); // 上の行で所有権が移動済なのでコンパイルエラーとなる

    let some2: Option<String> = Some("dummy".to_string());
    println!("{:?}", some2.as_ref().unwrap()); // as_refをはさむことで所有権の移動が発生しない
    println!("{:?}", some2.unwrap()); // この時点ではまだ所有権は移動していないのでコンパイルエラーとならない
    println!("{:?}", some2.as_ref().unwrap()); // 上の行で所有権が移動済なので参照も作れない(コンパイルエラー)
}

map

Option<T>Option<U>にする。

引数に渡した関数で変換・整形処理を行い、その結果としてOption<U>を返却する。

Noneに対して実行した場合、引数に渡した関数は実行されないことに注意。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.map(|s| { s.len() }));
    // -> Some(5)

    let none: Option<String> = None;
    println!("{:?}", none.map(|s| { s.len() }));
    // -> None(このときmapに渡した関数は実行されず、ただNoneという結果となる)
}

map_or

mapにデフォルト値がついたもの。

戻り値がOption型ではなく、中身のT型となることに注意。

mapと同様に、Noneの場合は引数に渡した関数は実行されないことに注意。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.map_or(100, |s| { s.len() }));
    // -> 5

    let none: Option<String> = None;
    println!("{:?}", none.map_or(100, |s| { s.len() }));
    // -> 100 (Noneのためmap_orに渡した関数は実行されない)
}

map_or_else

map_orのデフォルト値指定部分が関数になったもの。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.map_or_else(|| 100, |s| { s.len() },));
    // -> 5

    let none: Option<String> = None;
    println!("{:?}", none.map_or_else(|| 100, |s| s.len()));
    // -> 100 (Noneのためmap_or_elseに渡した第2引数の関数は実行されない)
}

ok_or

Option<T>Result<T,E>に変換する。

Some(T)の場合はOk(T)に、Noneの場合はErr(E)にそれぞれ変換される。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.ok_or(100));
    // -> Ok("dummy")

    let none: Option<String> = None;
    println!("{:?}", none.ok_or(100));
    // -> Err(100)
}

ok_or_else

ok_orで指定する引数が関数になったもの。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.ok_or_else(|| 100));
    // -> Ok("dummy")

    let none: Option<String> = None;
    println!("{:?}", none.ok_or_else(|| 100));
    // -> Err(100)
}

and / or / xor

and,or,xorという名前ですが、真理値表のように考えると混乱するかも。

ドキュメントにあるように以下のように動作することに注意。

andSome(T)の場合は引数に渡した値を、Noneの場合はNoneを返す
orSome(T)の場合はSome(T)自身を、Noneの場合は引数に渡した値を返す
xorどちらか一方のみがSome(T)のときのみSome(T)を返す
fn sample() {
    let some: Option<&str> = Some("some");
    let another_some: Option<&str> = Some("another_some");
    let none: Option<&str> = None;
    let another_none: Option<&str> = None;

    // and
    println!("{:?}", some.and(another_some)); // -> Some("another_some")
    println!("{:?}", some.and(none));         // -> None
    println!("{:?}", none.and(some));         // -> None
    println!("{:?}", none.and(another_none)); // -> None

    // or
    println!("{:?}", some.or(another_some)); // -> Some("some")
    println!("{:?}", some.or(none));         // -> Some("some")
    println!("{:?}", none.or(some));         // -> Some("some")
    println!("{:?}", none.or(another_none)); // -> None

    // xor
    println!("{:?}", some.xor(another_some)); // -> None
    println!("{:?}", some.xor(none));         // -> Some("some")
    println!("{:?}", none.xor(some));         // -> Some("some")
    println!("{:?}", none.xor(another_none)); // -> None
}

and_then / or_else

and, orの引数が関数になったもの。

fn sample() {
    let some: Option<&str> = Some("some");
    let another_some: Option<&str> = Some("another_some");
    let none: Option<&str> = None;
    let another_none: Option<&str> = None;

    // and_then
    println!("{:?}", some.and_then(|_| another_some)); // -> Some("another_some")
    println!("{:?}", some.and_then(|_| none));         // -> None
    println!("{:?}", none.and_then(|_| some));         // -> None
    println!("{:?}", none.and_then(|_| another_none)); // -> None

    // or_else
    println!("{:?}", some.or_else(|| another_some)); // -> Some("some")
    println!("{:?}", some.or_else(|| none));         // -> Some("some")
    println!("{:?}", none.or_else(|| some));         // -> Some("some")
    println!("{:?}", none.or_else(|| another_none)); // -> None
}

filter

引数に渡した関数の結果がfalseの場合はNoneを返却する。

Optionでチェーンにするときに活用できif文の削減につながる。

fn sample() {
    let some = Some("dummy".to_string());
    println!("{:?}", some.as_ref().filter(|v| v.len() == 5));
    // -> Ok("dummy")

    println!("{:?}", some.as_ref().filter(|v| v.len() == 2));
    // -> None

    let none: Option<String> = None;
    println!("{:?}", none.filter(|v| v.len() == 5));
    // -> None
}

get_or_insert

Option<T>の値がNoneの場合に引数に渡した値を含めたSome(T)を返す。

mut Option<T>な値に対して動作することに注意(get_or_insertを実行した変数の中身を変更する)

fn sample() {
    let mut some = Some("dummy".to_string());
    println!("{:?}", some.get_or_insert("another".to_string()));
    // -> "dummy"

    let mut none: Option<String> = None;
    println!("{:?}", none.get_or_insert("another".to_string()));
    // -> "another"

    println!("{:?}", none);
    // -> Some("another")  ※ 変数の値が変わっていることに注意
}

get_or_insert_with

get_or_insertの引数が関数になったもの。

fn sample() {
    let mut some = Some("dummy".to_string());
    println!("{:?}", some.get_or_insert_with(|| "another".to_string()));
    // -> "dummy"

    let mut none: Option<String> = None;
    println!("{:?}", none.get_or_insert_with(|| "another".to_string()));
    // -> "another"

    println!("{:?}", none);
    // -> Some("another")  ※ noneの値が変わっていることに注意
}

transpose

Option<Result<T,E>>Result<Option<T>,E>に変換する。

fn sample() {
    type Sample = Option<Result<i32, i32>>;

    let ok_some: Sample = Some(Ok(1));
    println!("{:?}", ok_some);              // -> Some(Ok(1))
    println!("{:?}", ok_some.transpose());  // -> Ok(Some(1))

    let err_some: Sample = Some(Err(1));
    println!("{:?}", err_some);             // -> Some(Err(1))
    println!("{:?}", err_some.transpose()); // -> Err(1)

    let none: Sample = None;
    println!("{:?}", none);                 // -> None
    println!("{:?}", none.transpose());     // -> Ok(None)
}

最後に

公式ドキュメントの情報は豊富ですが、やはりやってみないとわからないこともしばしば。

気になったことは実装して動かして見るに限りますね。

本記事では紹介していないメソッドもたくさんあり、新バージョンに向けた新しいメソッドもそこそこありますので、随時更新して情報を増やしていくつもりです。

もっと詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
目次