Rustの*_orと*_or_elseの違いと使い分け
RustのResultおよびOptionには*_orと*_or_elseという名前のメソッド、例えばmap_orとmap_or_else、unwrap_orとunwrap_or_elseなどがある。
*_orは先行評価で*_or_elseは遅延評価。関数呼び出しの結果を渡したい場合は*_or_elseの使用が推奨されている。
なお、便宜上*_orと*_or_elseと書いているが、orとelseやandとand_thenも同様。
*_orは先行評価で*_or_elseは遅延評価
公式リファレンスにおいて、*_orでは以下のように説明されている。以下はOption::unwrap_orだが、他も同じ。
Arguments passed to
unwrap_orare eagerly evaluated; if you are passing the result of a function call, it is recommended to useunwrap_or_else, which is lazily evaluated. Option::unwrap_or in core::option - Rust
*_orに渡された引数は先行評価され(eagerly evaluated)、*_or_elseでは遅延評価される(lazily evaluated)。関数呼び出しの結果を渡したい場合は*_or_elseの使用が推奨されている。
具体例で確認
具体例として、Optionのunwrap_orとunwrap_or_elseを使う。
Noneのとき、unwrap_orは指定された値を返し、unwrap_or_elseは指定されたクロージャや関数を実行しその値を返す。
動作を確認するために、文字列Stringを返す関数を定義しておく。いつ呼び出されたか分かるように、引数に指定した数値を出力する。
fn test_func(i: i32) -> String {
println!("Print from function: No.{i}");
String::from("N/A")
}
ベクタVecからpopで要素を取り出す。popはベクタが空のときNoneを返す。
要素数1個のベクタだと、1回目のpopはSome、2回目のpopはNoneを返す。
unwrap_orの場合、1回目も2回目も指定した関数が呼び出される。
let mut v: Vec<String> = vec![String::from("abc")];
assert_eq!(v.pop().unwrap_or(test_func(0)), "abc");
// Print from function: No.0
assert_eq!(v.pop().unwrap_or(test_func(1)), "N/A");
// Print from function: No.1
unwrap_or_elseの場合、2回目(Noneのとき)のみ関数が呼び出される。ここで|| ...は引数なしのクロージャ。
let mut v: Vec<String> = vec![String::from("abc")];
assert_eq!(v.pop().unwrap_or_else(|| test_func(2)), "abc");
assert_eq!(v.pop().unwrap_or_else(|| test_func(3)), "N/A");
// Print from function: No.3
unwrap_orの1回目の関数呼び出しは無駄なので、関数呼び出しの結果を渡したい場合はunwrap_or_elseの方がよい。
なお、Optionの*_or_elseに指定するクロージャや関数には何も渡されないが、Resultの*_or_elseに指定するクロージャや関数にはエラーEが渡される。
- Option::unwrap_or_else in std::option - Rust
where F: FnOnce() -> T
- Result::unwrap_or_else in std::result - Rust
where F: FnOnce(E) -> T
Resultの*_or_elseでは、処理に使わなくても引数を一つ受け取るクロージャや関数を指定する必要がある。例えばクロージャの場合|_| ...のようにする。
具体例は以下の記事を参照。