RustでOptionやResultの配列ができてしまったときの一般的なテク4つ
Vec<Result<_>>
ではなく Result<Vec<_>>
を得る
collect()
関数を使うと、 Vec<Result<_>>
を得ることもできるし、 Result<Vec<_>>
を得ることもできる。変換先の型を明示することで区別する。
fn main() { // 全てSomeならSome(配列)を返し、どれかがNoneなら全体もNoneになる assert_eq!([Some(1), Some(2)].iter().cloned().collect::<Option<Vec<_>>>(), Some(vec![1, 2])); assert_eq!([None, Some(2)].iter().cloned().collect::<Option<Vec<_>>>(), None); }
これができるのは以下の理由による。
FromIterator
は多対多である。つまり、ひとつの変換元に対して複数の変換先を定義できるようになっている。Option
とResult
は以下のFromIterator
を定義している。
impl<A, E, V: FromIterator<A>> FromIterator<Result<A, E>> for Result<V, E> { .. } impl<A, V: FromIterator<A>> FromIterator<Option<A>> for Option<V> { .. }
Some
/Ok
なものだけ抜き出す
flat_map
を使う。
fn main() { // 25を引き、引けなかったものは取り除く let v = [30u8, 40, 17, 80].iter() .flat_map(|&x| x.checked_sub(25u8)) .collect::<Vec<_>>(); assert_eq!(&v, &[5, 15, 55]); }
Result
と Option
はそれ自体が IntoIterator
である。(Ok
/Some
なら1要素、それ以外なら0要素として振る舞う。) そのため flat_map
が使える。
和や積を求める
Result
限定で Option
にはない。 Result<>
型のイテレーターに対して直接 sum
/product
を使うことができる。
fn main() { // 配列内の文字列をパースして、全て成功したらOk(和)、どれかが失敗したらErr let s = ["10", "20", "30"].iter() .map(|&s| s.parse::<i32>()) .sum::<Result<i32, _>>(); assert_eq!(s, Ok(60)); let s = ["10", "2o", "30"].iter() .map(|&s| s.parse::<i32>()) .sum::<Result<i32, _>>(); assert!(s.is_err()); }
これは Result
が以下のような Sum
/Product
の実装を持っているからである。
impl<T, U, E> Sum<Result<U, E>> for Result<T, E> where T: Sum<U> { .. } impl<T, U, E> Product<Result<U, E>> for Result<T, E> where T: Product<U> { .. }
失敗したらデフォルト値を入れる
単に要素ごとに unwrap_or
系関数を呼べばよい。
fn main() { // パースして失敗したら0にする let v = ["10", "2o", "30"].iter() .map(|&s| s.parse::<i32>()) .map(|r| r.unwrap_or(0)) .collect::<Vec<_>>(); assert_eq!(&v, &[10, 0, 30]); }