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)