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 は多対多である。つまり、ひとつの変換元に対して複数の変換先を定義できるようになっている。
  • OptionResult は以下の 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]);
}

ResultOption はそれ自体が 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]);
}