Rustのtry-catch構文

Rustのnightlyに新しく入ったtry-catch関連構文を紹介する。

do catch によるcatch構文

#![feature(catch_expr)]

use std::fs::File;
use std::io::{self, BufReader, Read};

fn main() {
    do catch {
        let f = File::open("foo.txt")?;
        let mut f = BufReader::new(f);
        let mut buf = String::new();
        f.read_to_string(&mut buf)?;
        println!("{}", buf);
        Ok(())
    }.unwrap_or_else(|err: io::Error| {
        eprintln!("An error occured: {}", err);
    })
}

catchはRust RFC 0243のtry-catch構文の一部として提案されていた。tryにあたるクエスチョンマークはRust1.13.0で安定化されたが、catchはまだ実装されていなかった。

catch構文は、これまた最近実装された値つき break (Rust RFC 1624) の亜種を用いて脱糖される。すなわち、

do catch {
    do catch {
        ...
        e1?
    }
    e2?
}
e3?

のようになっている場合、これは

'catch1: {
    'catch2: {
        ...
        match e2 { ... break 'catch2 err }
    }
    match e2 { ... break 'catch1 err }
}
match e3 { ... return err }

のようなHIRに脱糖される。ただし正確にはこのような構文は存在しない。ASTの構文上、ラベルをとることができるのは loop, for, while のいずれかである。また、実際には 'catch1 のようなラベルが付与されるのではなく、NodeIdで直接break先が識別される。

なお、 do は現時点では予約キーワードであり、 catch は弱キーワード(文脈依存キーワード)となる。 catch 単体ではレコード構造体の初期化構文と紛らわしいため、 do をつけることでそれを回避している。

? の一般化

? は現在の安定版では std::result::Result<T, E> の処理だけができるようになっている。以前のnightlyではこれを std::ops::Carrier というトレイトで一般化する実装が入っていたが、これは std::ops::Try という別のトレイトに置き換えられることになった。(Rust RFC 1859)

それぞれのトレイトの定義は以下の通りである。

pub trait Carrier {
    type Success;
    type Error;

    fn from_success(_: Self::Success) -> Self;
    fn from_error(_: Self::Error) -> Self;
    fn translate<T>(self) -> T where T: Carrier<Success=Self::Success, Error=Self::Error>;
}

pub trait Try {
    type Ok;
    type Error;

    fn into_result(self) -> Result<Self::Ok, Self::Error>;
    fn from_error(v: Self::Error) -> Self;
    fn from_ok(v: Self::Ok) -> Self;
}

名前のほかに異なる点として、 Carrier::translate に存在していた多相性がなくなって、よりシンプルになっている。

これにより、 e? は以下のように脱糖されることになる。

match ::std::ops::Try::into_result(e) {
    ::std::result::Result::Err(err) =>
        #[allow(unreachable_code)]
        // do catch {} の内側の場合
        break 'innermost_catch ::std::ops::Try::from_error(::std::convert::From::from(err)),
        // do catch {} の内側ではない場合
        return ::std::ops::Try::from_error(::std::convert::From::from(err)),
    ::std::result::Result::Ok(val) =>
        #[allow(unreachable_code)]
        val
}

残念ながら現在の実装では Result のみが Try を実装している。