Error型自作の実践編を書きました
記事はこちらです。よかったらご覧ください。
Error
を自作したい!
Threadから独自のError
を含んだResult
を返したくなった!
私は今までResult<T,String>
で誤魔化してきた。しかし、それはもうやめだ!
0からは、さすがにどうすればいいかわからないので、初めにerror::Error
を読んで見る。
error::Error
を読む
error::Error
トレイトとは、エラーの値を表す基本的な型であることを示しているトレイトです。エラーを表す型は、自分自身を説明できるように、Display
とDebug
を実装していなくてはなりません。
このトレイトが持つ
source
メソッドは一般的に'アブストラクト・バウンダー'1を超えたときに使われます。もし、あるモジュールが、低レベルのモジュールで発生したエラーによって引き起こされたエラーを報告するとき、source
メソッドによって、その低レベルのエラーへアクセスすることができます。これによって高レベルのモジュールは独自のError
を提供しながら、デバックのための実装をsource
のチェーンによって明らかにすることができます。
(以上、error::Error
の俺的意訳)
ざっくりいうと「error::Error
はResult<T,E>
のEに用いられるような型であることを示し、このトレイトの持つsource
メソッドを用いるとエラーをさかのぼることができる(さかのぼれるようにしなければならない)。」ということ。
error::Error
の非推奨なメソッド
次の2つのメソッドは非推奨あつかいになっています。
description
はエラーの説明を文字列で返すメソッドですが、説明が必要なときはこれを使わず、Display
やto_string
メソッドを使うことを推奨しています。
cause
はsource
に置き換えられるという形で非推奨になりました。
error::Error
には4つのメソッドがあり、その内、1つがNightlyで2つが非推奨。よって実際に書くのは1つだけですね!
error::Error
はわかったけど、どう実装すればいいの?
Error自作の肝であるerror::Error
についてはわかりました。しかし、どう実装すればいいのかわかりません。
そのためstd::io::Error
を読んで勉強します。
std::io::Error
の定義
std::io::Error
に関わる重要そうな定義を見ていく。
以下、ここから重要そうな場所の抜粋。
enum Repr {
Os(i32),
Simple(ErrorKind),
Custom(Box<Custom>),
}
struct Custom {
kind: ErrorKind,
error: Box<dyn error::Error + Send + Sync>,
}
pub struct Error {
repr: Repr,
}
impl Error {
pub fn new<E>(kind: ErrorKind, error: E) -> Error
where
E: Into<Box<dyn error::Error + Send + Sync>>
{
Self::_new(kind, error.into())
}
fn _new(kind: ErrorKind, error: Box<dyn error::Error + Send + Sync>) -> Error {
Error { repr: Repr::Custom(Box::new(Custom { kind, error })) }
}
}
impl Debug for Error { // something }
impl Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.repr {
Repr::Os(code) => {
let detail = sys::os::error_string(code);
write!(fmt, "{} (os error {})", detail, code)
}
Repr::Custom(ref c) => c.error.fmt(fmt),
Repr::Simple(kind) => write!(fmt, "{}", kind.as_str()),
}
}
}
impl error::Error for Error{
fn description(&self) -> &str {
match self.repr {
Repr::Os(..) | Repr::Simple(..) => self.kind().as_str(),
Repr::Custom(ref c) => c.error.description(),
}
}
fn cause(&self) -> Option<&dyn error:Error> {
match self.repr {
Repr::Os(..) => None
Repr::Simple(..) => None,
Repr::Custom(ref c) => c.error.cause(),
}
}
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self.repr {
Repr::Os(..) => None,
Repr::Simple(..) => None,
Repr::Custom(ref c) => c.error.source(),
}
}
}
enum ErrorKind{
NotFound,
PermissionDenied,
// etc
}
impl ErrorKind {
pub(crate) fn as_str(&self) -> &'static str {
// エラーの種類ごとにmatchで、そのエラーの概要を
// 表す文字列に変えて返す。
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { repr: Repr::Simple(kind) }
}
}
std::io::Error
が表す種類
実装を見るとstd::io::Error
は大雑把にOs、Simple、Customの3種類のエラーを表すことがわかります。
詳しく見ていませんが私が想像するに
種類 | 説明 |
---|---|
Os | OSレベルのエラーを表す |
Simple | std::ioの関数内で発生したエラーを表す |
Custom | 上2つ以外の独特なエラーを表す |
だと、予想しました。
Osを表すstd::io::Error
の作成
Osを表すstd::io::Error
を作るにはlast_os_error
かfrom_raw_os_error
を実行します。
この種のエラーについては、Error自作の基本にかかわらないため、スルーします。2
Simpleを表すstd::io::Error
の作成
Simpleを表すstd::io::Error
はFrom<ErrorKind>
が実装されてるので、これを用います。
// 実装
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { repr: Repr::Simple(kind) }
}
}
// 例
let error = Error::from(ErrorKind::NotFound);
Customを表すstd::io::Error
の作成
Customを表すstd::io::Error
はnew
から作られます。
//実装
impl Error {
pub fn new<E>(kind: ErrorKind, error: E) -> Error
where
E: Into<Box<dyn error::Error + Send + Sync>>
{
Self::_new(kind, error.into())
}
fn _new(kind: ErrorKind, error: Box<dyn error::Error + Send + Sync>) -> Error {
Error { repr: Repr::Custom(Box::new(Custom { kind, error })) }
}
}
// 例 文字列からエラーを作成する
let custom_error = Error::new(ErrorKind::Other, "oh no!");
// 例 他のエラーからエラーを作成する
let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error);
new
を読んでみると、次のようなことが書かれています。
任意のエラー内容と同様の既知の種類のエラーからI/Oエラーを作成します。
この関数は一般的にOSから来たオリジナルではないI/Oエラーを作るために使用されます。
error
の引数は、この型に含まれる任意の内容が入れられます。
(以上、new
の俺的意訳)
なるほど。つまり、OSからのエラーなどの低レベルから連鎖してきたエラーなら、new
を使って作成ということですね。
そしてCustom
は、低層からのエラーと自身のいる層のエラー、の両方を含むものだよ。と。
補足:Into<Box<dyn error::Error + Send + Sync>>
のブランケット実装
Box<dyn Error + Send + Sync + 'a>
は他のError
や&str
からFrom
で作成することができます。(他のError
や&str
はリンクになってます。)
すなわち、他のError
や&str
にはInto<Box<dyn error::Error + Send + Sync>>
がブランケット実装されているのです。
これによって例のようなnew
ができるわけですね。
改めてerror::Error
を考える
ここで、ちょっとstd::io::Error
のerror::Error
実装部分を読んで、description
、cause
、source
について復習します。
実装は次の通りです。
impl error::Error for Error{
fn description(&self) -> &str {
match self.repr {
Repr::Os(..) | Repr::Simple(..) => self.kind().as_str(),
Repr::Custom(ref c) => c.error.description(),
}
}
fn cause(&self) -> Option<&dyn error:Error> {
match self.repr {
Repr::Os(..) => None
Repr::Simple(..) => None,
Repr::Custom(ref c) => c.error.cause(),
}
}
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self.repr {
Repr::Os(..) => None,
Repr::Simple(..) => None,
Repr::Custom(ref c) => c.error.source(),
}
}
}
確かに、cause
、source
も同じような実装になっています。
またDisplay
の実装を見てみると
impl Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.repr {
Repr::Os(code) => {
let detail = sys::os::error_string(code);
write!(fmt, "{} (os error {})", detail, code)
}
Repr::Custom(ref c) => c.error.fmt(fmt),
Repr::Simple(kind) => write!(fmt, "{}", kind.as_str()),
}
}
}
となっており、description
と同じ働きをしているのがわかります。
まとめ
自作Error(error::Error
を実装した自作の型)を作るには
-
対応したいエラーが、他のエラーによって誘起されるかどうか考える。
-
エラーの種類を表す型(
ErrorKind
等)を作成する。
-> もし他のエラーから誘起されるのなら誘起元のエラーを格納できるようにする。 -
上の2番で作ったものを格納する自作Errorを定義する。
-
自作Errorに
error::Error
を実装する。
-> もし他のエラーに誘起されるのならsource
で誘起元のエラーを返せるようにする。
-> (source
にはデフォルトの実装が定義されているため誘起されないなら不要) -
おわり。♪(((^-^)八(^∇^)))♪