🔀

`Option<T>` のよく使うメソッドを書いてみる

に公開

Option<T> のよく使うメソッドを書いてみる

前回Option<T> の基本的な使い方と bool に変換するメソッドを紹介しました。

今回は Option<T> のメソッドのうち、ドクターメイト株式会社のコードベースでよく使われているものを感覚的に選び、グループ化しつつ適当に書き出してみます。

全部のメソッドは紹介しません。たとえば、ドクターメイト株式会社の運用上 &mut Option<T> を使用していないので、そういったメソッドは出てきません。

では、さっそくはじめましょう。

Option<T>T にする (中身を取り出す) メソッド

expect / unwrap

expectunwrap は典型的な unwrap のためのメソッドです。 Option<T> の中身 T を取り出したいときに使います。

unwrapNone のとき panic します。 expect も同様ですが、こちらはメッセージをつけることができます。

let o: Option<i32> = Some(123);
assert_eq!(o.unwrap(), 123);

let o: Option<i32> = None;
o.unwrap(); // panic
// called `Option::unwrap()` on a `None` value
let o: Option<i32> = Some(123);
assert_eq!(o.expect("o is Some"), 123);

let o: Option<i32> = None;
o.expect("o is Some"); // panic
// o is Some

unwrap_or / unwrap_or_default / unwrap_or_else

unwrap_orNone の場合の値を指定することで unwrap します。 unwrap_or_defaultNone の場合の値として Default::default の値を使用します。 unwrap_or_elseNone の場合の値の代わりに値を返す関数を指定します。

let x: i32 = Some(123).unwrap_or(234);
assert_eq!(x, 123);

let y: i32 = None.unwrap_or(234);
assert_eq!(y, 234);

let z: i32 = None.unwrap_or_default();
assert_eq!(z, 0);
assert_eq!(<i32 as Default>::default(), 0);

let max: i32 = None.unwrap_or_else(|| i32::MAX);
assert_eq!(max, i32::MAX);

ドクターメイト株式会社での unwrap の運用

ドクターメイト株式会社では unwrap をテストコードでも使用しないようにしています。理由は、テストコードで unwrap を使用していると、プロダクションコード側で誤って unwrap の使用を残してしまっていても検索でそれに気づくことが難しくなるためです。安全に unwrap できる場合は代わりに expect を使用しています。

Option<T>Option<U> にする (中身を置き換える) メソッド

map

Option<T> を分解して再構築するのはできますが面倒です。 T -> U な関数と Some(T)Some(U) をつくりたいですね。

let o: Option<i32> = Some(123);
assert_eq!(o.map(|it| it * 2), Some(246));

let o: Option<i32> = None;
assert_eq!(o.map(|it| it * 2), None);

ちなみに map_ormap_or_else は名前のイメージに反して、中身を置き換えるというよりは unwrap する処理です。 or は None の場合の値を指定するので結果的に Option<T> ではなくなるということですね。

また map_ormap_or_else は意外と使われていなさそうです。おそらく ok_orunwrap_or などを使っているのではないかと思います。

and_then

and / and_then / or / or_else 。とりあえずセットっぽいものを挙げてみたものの、 and_then 以外はほとんど使われていないようです。

and_then はいわゆる flatmap あるいは bind です。さすがに、わりと使われています。

T -> Option<U> な関数と Some(T)NoneSome(U) をつくりたいこともありますね。

map の中で Option を返す関数を呼び出す状況 (たとえば Option<Vec<T>>Vec::get する、とか) などです。

let o: Option<Vec<i32>> = Some(vec![10, 20, 30]);
assert_eq!(o.and_then(|it| it.get(0).copied()), Some(10));

let o: Option<Vec<i32>> = Some(vec![10, 20, 30]);
assert_eq!(o.and_then(|it| it.get(4).copied()), None);

ちなみに flatmap ではなく flat 単体の flatten は意外と使われていないです。これはおそらく _.map(_).flatten(_) だと clippy に怒られるためだと思います。

https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten

また or_else も思ったほど使われていないです。 or_else を使いそうな場面では unwrap_or_elseOption<T>T にしてから操作しているのかもしれません。

as_deref / as_ref

中身を置き換えるといってもいろいろで、「参照」に置き換えたいケースもあります。

慣れていないと「 as_ref で十分かなー」という気になりますが、 Option<String>Option<&str> にしたいケースはよくあるので as_deref も必要なことが多いです。

例えば option_string.as_deref().map(V::from_str) です。 as_ref だと Option<&String> にしかならないので as_deref が欲しくなります。

Option<T>Result に変換するメソッド

まだ Result について書いていないのですが、流れ的に。

ok_or / ok_or_else

OptionResult に変換するものです。 or (None の場合) の E や、それを返す関数を指定します。

let o: Option<i32> = Some(123);
assert_eq!(o.ok_or(456), Ok(123));

let o: Option<i32> = None;
assert_eq!(o.ok_or(456), Err(456));

let o: Option<i32> = Some(123);
assert_eq!(o.ok_or_else(|| 456), Ok(123));

let o: Option<i32> = None;
assert_eq!(o.ok_or_else(|| 456), Err(456));

transpose

最後は transpose です。 これを書きたいがために Option を記事にしたところもあります。

transposeOption<Result<T, E>>Result<Option<T>, E> にしてくれます。 Option の内側にある Result を外側に「転置」してくれるわけですね。

map の中で Result を返す関数を使うと Option<Result<T, E>> になりますが、不慣れなユーザーだと「 Result? を使いたいのに……」と戸惑ったり、 match で分解してみたり……と迷いがちです。そこで transpose です。

例えば option_string.as_deref().map(V::from_str).transpose()? です。これは定型句です。 Option<String>FromStr を実装した任意の型 (たとえば Value Object) に変換します。

#[derive(Debug)]
struct V(String);

impl std::str::FromStr for V {
    type Err = ();
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(s.to_owned()))
    }
}

use std::str::FromStr as _;

// Option<String> を .as_deref() で Option<&str> にして、
// Option<&str> を .map(V::from_str) で Option<Result<V, _>> にして、
// Option<Result<V, _>> を .transpose() で Result<Option<V>, _> にして、
// Result<Option<V>, _> を ? で Option<V> にする
let o: Option<String> = Some("user input".to_owned());

let o: Option<V> = o.as_deref().map(V::from_str).transpose()?; // 定型句

println!("{:?}", o); // => Some(V("user input"))

最後に

前回 と今回の 2 回で Option<T> について書きました。

リファレンスを眺めながら書いてみました。たまにリファレンスを見ると、こんなのあったんだと思うことが多くて面白いですね。

既に使っている方からの反応が知りたいですね……。「確かによく使う」や「(ここに挙げられなかった)このメソッドは使わないの?」など。

GitHubで編集を提案
ドクターメイト

Discussion