Rust

OptionをResultにする

この記事は入門向けの記事になっています。

Rustが進化して行くに連れて、バージョン1以降になってからも変化がありました。個人的にその変化の一つが ?オペレータの登場です。

fn foo() -> Result<(), String> {
    Err("error".to_owned())?;
    println!("no print");
    Ok(())
}
println!("{:?}", foo());

このオペレータは、try!マクロと同様の動きをします。Rustの多くのプログラマは、このオペレータが登場する前にtry!マクロを活用していました。これからは?が多く使われることでしょう。

他の言語では、? はOptionalとして扱われるケースが多いため、Rustの場合は例外時にreturnされることと、OptionではなくResultであることに注意が必要です。

Option型は ? が使えない

さて、ここで本題に入ります。Rustのcrateによっては、ResultではなくOptionで結果を返すライブラリも多くあります。Optionの場合は、? を使うことが出来ないため、Resultに変換する必要があります。

追記(2018/03/05) 使えるようになっています。

ここから先にいくつか悪い例を紹介し、最後に良い例を紹介します。

matchで変換

fn foo() -> Result<(), String> {
    let opt: Option<String> = None;
    match opt {
        Some(s) => Ok(s),
        None => Err("error".to_owned())
    }?;
    println!("no print");
    Ok(())
}
println!("{:?}", foo());

型でマッチさせたあとにResultを作っています。ちょっと長いです。

unwrap系(動かない)

fn test() {
    fn foo() -> Result<(), String> {
    let opt: Option<String> = None;
    opt.unwrap_or(Err("error".to_owned()))?;
    println!("no print");
    Ok(())
}
println!("{:?}", foo());

こちらは、 expected struct `std::string::String`, found enum `std::result::Result` が発生し失敗します。
unwrap系は、成功した場合、self を返すため、型がどうしてもOptionになってしまいます。

ok_or

fn foo() -> Result<(), String> {
    let opt: Option<String> = None;
    opt.ok_or("error".to_owned())?;
    println!("no print");
    Ok(())
}
println!("{:?}", foo());

Optionには、ok_or<E>(self, err: E) があり、selfOption<T> から Result<T, E> に変換されて返されます。OptionはErrを持っていないため、Resultに変換する際に引数をErrとして渡しているところがポイントです。

OptionをResultに変換する場合は、ok_or を使うとスッキリ書くことが出来ます。他にも、ファンクションが渡せる ok_or_else があります。