try_trait_v2でOptionとResult両方に?を使う
この前 NoneError
(try_trait
(v1) の実装) が消えていたのを見て、どうやら try_trait_v2
に移行したらしいということで勉強した。
参考:RFC https://rust-lang.github.io/rfcs/3058-try-trait-v2.html
Result<T, E>
の Ok(t)
については t: T
があれば良いが、 Err(e)
については型情報を持ったまま return に処理を移すために e: E
ではなく Err(e): Result<Infallible, E>
を "residual" として残すことになるというのが v2 のポイント(v1のときに不勉強だったのに差分を説明……)。もちろん、この2通り(early-return しないかするか)は enum で表現され、なぜか Result::{Ok, Err}
ではなく ControlFlow::{Continue, Break}
が使われる。
Option, Result 両方を ?
できる関数を書くには Option<Infallible>
, Result<Infallible, E>
について FromResidual
を実装した型をつくる。コード例では OptionResult<T, E>
としたが、 struct Foo(Option<Result<T, E>>)
のようにもできるはず(いずれにせよ orphan rule により新しい型が必要だし、そもそも Option<_>
や Result<_, _>
はすでに impl Try
されてしまっていて重複不可)。
#![feature(try_trait_v2, try_blocks)] // try_blocks は使用例のみで使う。 use std::{ convert::Infallible, ops::{ControlFlow, FromResidual, Try}, }; #[derive(Debug)] enum OptionResult<T, E> { Ok(T), Err(E), None, } impl<T, E> FromResidual<Option<Infallible>> for OptionResult<T, E> { fn from_residual(_: Option<Infallible>) -> Self { OptionResult::None } } impl<T, E, E0> FromResidual<Result<Infallible, E0>> for OptionResult<T, E> where E: From<E0>, { fn from_residual(e: Result<Infallible, E0>) -> Self { OptionResult::Err(e.unwrap_err().into()) } }
また、残念ながら、 -> OptionResult<T, E>
な関数に ?
を使っていくにはこの型自身の値も ?
できる必要があることになっている。すなわち Try
trait を以下のように実装する(使わないなら todo!
で十分)。
impl<T, E> Try for OptionResult<T, E> { type Output = T; type Residual = OptionResult<Infallible, E>; fn from_output(t: T) -> Self { OptionResult::Ok(t) // ここも todo!() にしても良いが、 try block で便利 } fn branch(self) -> ControlFlow<Self::Residual, T> { todo!() } } impl<T, E> FromResidual<<Self as Try>::Residual> for OptionResult<T, E> { fn from_residual(_: <Self as Try>::Residual) -> Self { todo!() } }
なんでも Result<Option<T>, Box<dyn std::error::Error>>
にするのが使い勝手良さそうということで、使用例は以下のとおり。
fn main() { let mut xs = "42,3,foo,7".split(','); for _ in 0..6 { println!("{:?}", f(&mut xs)); } } fn f<'a>( it: &mut impl Iterator<Item = &'a str>, ) -> Result<Option<i32>, Box<dyn std::error::Error>> { let res: OptionResult<_, _> = try { it.next()?.parse::<i32>()? + 1 }; res.into_result() } impl<T, E> OptionResult<T, E> { fn into_result(self) -> Result<Option<T>, E> { match self { Self::Ok(t) => Ok(Some(t)), Self::Err(e) => Err(e), Self::None => Ok(None), } } }
Ok(Some(43)) Ok(Some(4)) Err(ParseIntError { kind: InvalidDigit }) Ok(Some(8)) Ok(None) Ok(None)