合成したエラー型を使う
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
を実装するのかが記述されている。
DecodeError
やIOError
には元のエラーの他に追加で情報を記録することもできるが、今回は単純にして、さらに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の和訳を参照してください
-
nurseryは「養成所」の意味。Rust本体にあった方が良い機能をrust本体とは別に作っている。 ↩