Rust by Example

16.10 box化を再度可能にする

独自のエラー型に対してDisplayFromを実装することで、標準ライブラリのエラーハンドリング機能のほぼすべてが使用できるようになりました。「ほぼ」というのはつまり、エラー型をbox化する機能がまだ備わっていないためです。

すなわち、標準ライブラリはFromによって、Errorトレイトを実装しているあらゆる型をBox<Error>というトレイトオブジェクトへと変換します。ライブラリのユーザにとっては、これは以下のような書き方ができて便利です。

// 自動的に`Box<Error>`に変換できるエラー型ならばどんなものでもここで使用できる。
fn foo(...) -> Result<T, Box<Error>> { ... }

例えば、ユーザーはそれぞれが独自のエラー型を提供している種々のライブラリを使用することがあるでしょう。有効なResult<T, E>を定義する方法として、ユーザーにはいくつかの選択肢があります。

  • 外部ライブラリのエラー型をラップする新しいエラー型を定義する。
  • Stringなどの別の型にいったん変換して仲介させる。
  • 型消去によってBox<Error>に変換する。

普通はボックス化する方法を用います。唯一のペナルティは実際の(訳注: Box内のErrorトレイトを実装している)エラー型が静的に決定されているわけではないため、ランタイムまでわからないという点です。これを可能にするために唯一必要なのは、Errorトレイトを実装することです。

trait Error: Debug + Display {
    fn description(&self) -> &str;
    fn cause(&self) -> Option<&Error>;
}

これを実装することで、前項の例はDoubleErrorを用いる以前と同様に、Box<Error>に対して有効になります。

use std::error; use std::fmt; use std::num::ParseIntError; // `Box<error::Error>`へのエイリアスに変更 type Result<T> = std::result::Result<T, Box<error::Error>>; #[derive(Debug)] enum DoubleError { EmptyVec, Parse(ParseIntError), } impl From<ParseIntError> for DoubleError { fn from(err: ParseIntError) -> DoubleError { DoubleError::Parse(err) } } impl fmt::Display for DoubleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DoubleError::EmptyVec => write!(f, "please use a vector with at least one element"), DoubleError::Parse(ref e) => e.fmt(f), } } } impl error::Error for DoubleError { fn description(&self) -> &str { match *self { // エラー内容の非常に簡潔な説明。 // `Display`と同じである必要はない。 DoubleError::EmptyVec => "empty vectors not allowed", // これはすでに`Error`を実装しているので、その実装を用いる。 DoubleError::Parse(ref e) => e.description(), } } fn cause(&self) -> Option<&error::Error> { match *self { // 隠れた原因があるわけではないので`None`を返す。 DoubleError::EmptyVec => None, // 原因はParseIntError。`&error::Error`というトレイトオブジェクトに // 暗黙的に変換される。これが可能なのはParseIntErrorがすでに`Error` // トレイトを実装しているため。 DoubleError::Parse(ref e) => Some(e), } } } fn double_first(vec: Vec<&str>) -> Result<i32> { let first = try!(vec.first().ok_or(DoubleError::EmptyVec)); let parsed = try!(first.parse::<i32>()); Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }

See also:

動的ディスパッチErrorトレイト