`Option<T>` のよく使うメソッドを書いてみる
Option<T>
のよく使うメソッドを書いてみる
前回 は Option<T>
の基本的な使い方と bool
に変換するメソッドを紹介しました。
今回は Option<T>
のメソッドのうち、ドクターメイト株式会社のコードベースでよく使われているものを感覚的に選び、グループ化しつつ適当に書き出してみます。
全部のメソッドは紹介しません。たとえば、ドクターメイト株式会社の運用上 &mut Option<T>
を使用していないので、そういったメソッドは出てきません。
では、さっそくはじめましょう。
Option<T>
を T
にする (中身を取り出す) メソッド
expect
/ unwrap
expect
と unwrap
は典型的な unwrap のためのメソッドです。 Option<T>
の中身 T
を取り出したいときに使います。
unwrap
は None
のとき 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_or
は None
の場合の値を指定することで unwrap します。 unwrap_or_default
は None
の場合の値として Default::default
の値を使用します。 unwrap_or_else
は None
の場合の値の代わりに値を返す関数を指定します。
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_or
や map_or_else
は名前のイメージに反して、中身を置き換えるというよりは unwrap する処理です。 or は None
の場合の値を指定するので結果的に Option<T>
ではなくなるということですね。
また map_or
や map_or_else
は意外と使われていなさそうです。おそらく ok_or
や unwrap_or
などを使っているのではないかと思います。
and_then
and
/ and_then
/ or
/ or_else
。とりあえずセットっぽいものを挙げてみたものの、 and_then
以外はほとんど使われていないようです。
and_then
はいわゆる flatmap あるいは bind です。さすがに、わりと使われています。
T -> Option<U>
な関数と Some(T)
で None
や Some(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_else
で Option<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
Option
を Result
に変換するものです。 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
を記事にしたところもあります。
transpose
は Option<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>
について書きました。
リファレンスを眺めながら書いてみました。たまにリファレンスを見ると、こんなのあったんだと思うことが多くて面白いですね。
既に使っている方からの反応が知りたいですね……。「確かによく使う」や「(ここに挙げられなかった)このメソッドは使わないの?」など。
Discussion