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
と言うメソッドもありますね。