いろんな Error 実装がある
Rust には、成功と失敗を表現するための標準的な型である std::result::Result 型が用意されています。
Result
を返す関数内でエラーが発生した場合は、Err
バリアントのフィールドに具体的なエラーオブジェクトを詰めて返すことになるのですが、このエラーオブジェクトの型として、各ライブラリが独自のエラー型を定義しています。
下記はその一例です。
同じエラー型を使っているつもりでも、上記のように実体は異なる定義だったりするので注意してください。
Error トレイト
Rust は共通のエラーインタフェースとして、次のような std::error::Error トレイト を定義しています。
前述の各種エラー型は、この Error
トレイトを実装しています。
pub trait Error: Debug + Display {
fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
fn description(&self) -> &str { ... }
fn cause(&self) -> Option<&dyn Error> { ... }
fn provide(&'a self, demand: &mut Demand<'a>) { ... }
}
Error
は、Debug
と Display
も備えていることがわかります。
そのため、ほとんどのエラー型は、次のような共通のコードで解析することができます。
println!("ERROR: {}", err); // Display による簡潔なエラー情報
println!("ERROR: {:?}", err); // Debug によるデバッグ情報(技術的な情報)
err.source()
メソッドを使えば、そのエラー型の発生原因となったエラー型を取得できます。
戻り値は Option
型になっており、自分自身のエラー型がルートの発生源であれば、err.source()
は None
を返します。
☝️ 独自のエラー型を定義する場合の推奨方法
- Error トレイトを実装する
std::error::Error
トレイトを実装することで、共通のメソッドを使ってエラーを解析できるようになります。例えば、Error#source
メソッドでエラーの発生源を調べることができます。source
の実装は、内部で保持しているError
オブジェクトを返すだけで済みます。
- Display と Debug を実装する
- これらを実装することで、クライアントがエラーの内容を出力できるようにしておきます(
Error
トレイトを実装するとき、自動的にこれらの実装も必要になります)。 Display
の内容は、1 行で簡潔に「何が悪かったのか」分かるような表現にします。 他のレポートにネストされる形で表示されたりするので、基本的には すべて小文字 で、末尾のピリオドは付けない ようにします。Debug
が提供する情報は、Display
よりも具体的になるようにします。例えば、オープンに失敗したファイルの名前や、ポート番号などの情報を含めます。多くのケースでは、#[derive(Debug)]
を採用できます。
- これらを実装することで、クライアントがエラーの内容を出力できるようにしておきます(
- Send と Sync を実装する
- 可能であれば、スレッド境界を越えられるように
Send
とSync
を実装しておきます。エラー型がスレッドセーフになっていなければ、きっとそのクレートはマルチスレッドなコンテキストでは利用できません。
- 可能であれば、スレッド境界を越えられるように
- static なライフタイムを持たせる
'static
にすることで、エラー情報を用意にコールスタックに乗せることができるようになります。