Option.mapの中でResult型を返したい

業務でRustを書いている時に受けた質問を元に一記事書きます。

Rustを書いているとたまにイテレータのmapメソッドの中でResult型の値を返したい時があります。

例えば以下のようなコード

fn i32_to_u16(x: Option<i32>) -> anyhow::Result<Option<u16>> {
    x.map(|x| x.try_into().with_context(|| "変換エラー"))
}

このコード自体はコンパイルエラーになりますがやりたいことは分かるかと思います。

i32型の取り得る値は-2,147,483,648~2,147,483,647の範囲、u16型の取り得る値は0~65,535の範囲です。

このため i32-> u16の変換ではエラーが発生する可能性があり、変換には .try_intoを用いる必要があります。

しかしこの時 .mapメソッドの返す値の型は Option<Result<u16>>となり、求めている Result<Option<u16>>とはなりません。

x.try_into()が返すのは Result<u16>型なので x.map(|x| x.try_into()?)というように ?を使ってunwrapしてやれば良いと思うのですが、これも下記のようなエラーとなるためできません。

?オペレータをクロージャの中で使用する時は、クロージャResult型あるいはOption型を返さないといけないというものです。

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
   |
18 |     x.map(|x| x.try_into()?)
   |           ---             ^ cannot use the `?` operator in a closure that returns `u16`
   |           |
   |           this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Result<Infallible, TryFromIntError>>` is not implemented for `u16`

解決策

Option<Result<T>>型を Result<Option<T>>に、あるいは Result<Option<T>>型を Option<Result<T>>型に変換するための .transposeメソッドを使用ことでこの問題が解決できます

このメソッドを使うと冒頭のコードは以下のように書くことができます。

fn i32_to_u16(x: Option<i32>) -> anyhow::Result<Option<u16>> {
    x.map(|x| x.try_into().with_context(|| "変換エラー")).transpose()
}

この辺りのことはresultモジュールあるいはoptionモジュールのドキュメントに書いてあります

https://doc.rust-lang.org/std/result/

https://doc.rust-lang.org/std/option/

同じようなメソッドとして Option<Option<T>>Option<T>に、あるいは Result<Result<T>>Result<T>に変換するための .flattenと言うメソッドもありますね。