LoginSignup
66
43

More than 5 years have passed since last update.

Rustでエラーを合成する

Last updated at Posted at 2017-03-13

合成したエラー型を使う

Rustには例外がない。
全ての失敗するかもしれない計算はOptionやResultを使って処理する。

use std::{fs, io};

fn read_file(filename: &str) -> Result<String, io::Error> {
    let mut f = fs::File::open(filename)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

?演算子が実装されたことで記述も簡単になった。
この例では二つの?が使用されているが、これらはともにio::Errorを返すのでResult<String, io::Error>を使用できる。
しかし、別のエラーが発生する場合はどうだろう?例えばデータがmsgpackで保存されていたとすると:

use serde::Deserialize;
use rmp_serde::{decode,Deserializer};

fn read_msg<'de, T: Deserialize<'de>>(filename: &str) -> Result<T, ???> {
    let buf = fs::File::open(filename)?;          // may throw io::Error
    let mut de = Deserializer::new(&buf);
    let obj = Deserialize::deserialize(&mut de)?; // may throw decode::Error
    Ok(obj)
}

もちろんこのコードは動かない。Resultがちゃんと定義されていないからだ。
ではここにはどんなエラーを入れればいいのだろうか?以前はBox<std::error::Error>を使用していたが、準標準(?)ライブラリのrust-lang-nursery/failureではfailure::Errorという型が用意してある1

extern crate failure;

fn read_msg<'de, T: Deserialize<'de>>(filename: &str) -> Result<T, failure::Error> {
    let buf = File::open(filename)?;
    let mut de = Deserializer::new(&buf);
    let obj = Deserialize::deserialize(&mut de)?;
    Ok(obj)
}

このfailure::Errorは標準ライブラリのエラー型の他、次に説明するfailure::Fail traitを実装したエラーを取れるように出来ているため、簡単にエラーを合成して使う事ができる。

エラー型を自分で定義する

単純なプロジェクトではfailure::Errorで十分だろう。しかしライブラリとして提供する場合、
また複数人数で大規模なプロジェクトではちゃんとしたエラー型を定義しておく方が都合が良い。
failureではこのためにFail traitが用意してある:

#[macro_use]
extern crate failure;

#[derive(Debug, Fail)]
enum LoadError {
    #[fail(display = "Msgpack decode failed: {:?}", error)]
    DecodeError {
        error: decode::Error,
    },

    #[fail(display = "Some I/O Error: {:?}", error)]
    IOError {
        error: io::Error,
    }
}

このようにfailureではFail traitをproc_macroによって自動実装している。#[fail(display = "...")]の行ではこのエラーにどのようにDisplayを実装するのかが記述されている。

DecodeErrorIOErrorには元のエラーの他に追加で情報を記録することもできるが、今回は単純にして、さらにFromで変換することにしよう:

impl From<decode::Error> for LoadError {
    fn from(error: decode::Error) -> Self {
        LoadError::DecodeError { error }
    }
}

impl From<io::Error> for LoadError {
    fn from(error: io::Error) -> Self {
        LoadError::IOError { error }
    }
}

?演算子ではエラー型には自動的にIntoが適用されるので以下のように使える:

fn read_msg<'de, T: Deserialize<'de>>(filename: &str) -> Result<T, LoadError> {
    let buf = File::open(filename)?;               // 自動でio::Error -> LoadErrorに変換される
    let mut de = Deserializer::new(&buf);
    let obj = Deserialize::deserialize(&mut de)?;  // こっちも
    Ok(obj)
}

これでfailure::Errorを使った場合と同様のインターフェースで使える(/・ω・)/

2017/8/21追記

enum-error-deriveは他のproc_macroを追加するため、v0.2からproceduralsにリネームされました。
enum-error-derive v0.1.1は引き続き利用可能ですが、メンテされません。

2018/5/28追記

現在では公式にfailureというライブラリがあるのでこちらを使うのがおすすめです

#[derive(Debug, Fail)]
enum ToolchainError {
    #[fail(display = "invalid toolchain name: {}", name)]
    InvalidToolchainName {
        name: String,
    },
    #[fail(display = "unknown toolchain version: {}", version)]
    UnknownToolchainVersion {
        version: String,
    }
}

Fail traitが導入されて色々使いやすくなってます(/・ω・)/

2018/8/4更新

  • failure向けに書き直し
  • serde 1.0向けのコードに修正

2019/3/10追記

failureでの実験を経て標準ライブラリのErrorトレイトが拡張されます。詳しくは以下のRFCの和訳を参照してください


  1. nurseryは「養成所」の意味。Rust本体にあった方が良い機能をrust本体とは別に作っている。 

66
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
43