Help us understand the problem. What is going on with this article?

Rustでエラーを合成する

More than 1 year has passed since last update.

合成したエラー型を使う

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本体とは別に作っている。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした