trackableの改善版を考えてみる
慣習的に「一つのcrateに、一つの独自のError型」という方式を取っているが、crate境界を跨いだ場合の変換が面倒
独自のError型が、自分のcrate内で定義されているものであればまだFromトレイトを利用する形で変換処理をまとめられるのでまだ良いが、そうではない場合にはかなり手間
track!マクロの使用箇所で、map_err等を用いて都度変換コードを記述する必要がある
ErrorKindは使う時には使うのだけれど、大半の場合には使われない(単にトップレベルでエラーを表示するだけ)
もともとエラー処理以外も想定したインタフェースになっているけれど、実際にはエラー処理にしか使っていないので、それなら用途を割り切った方がシンプルになりそう
track!マクロを各呼び出しで記述するのが結構手間(これは現状は仕方がないと思っているが一応)
「一つのcrateに一つのError」というのが結構使い難いケースがある
複数crateで同じErrorを共有したい場合もある(その時は上述の問題にぶつかる)
crate内でも、メソッドによってもっと細かくエラーを分けたい時もある
プロセスやスレッド、future境界を跨いでも、エラーが追跡可能 (stacktraceでは無理)
backtraceだとノイズが多いのと、環境依存(少なくとも昔はwindowsだとまともに使えない時期があった) track!マクロファミリーが結構便利
途中で付加情報を追加できるのも便利(調査時に)
Cloneを実装可能(サーバ用途でたまに便利)
解決案
以下のようなcrateを実装してみる:
もっと"エラー"を強調した名前にすべき? ("trackerr"?)
ただしErrorKind的なものはなく、全てのcrateで共通のtracker::Errorを使用する想定
上記のエラー型はroot_causeとcauseメソッドを提供する
前者はiter_causesでも十分かもしれない
利用者は、これらのメソッドとダウンキャストを組み合わせて、必要であれば独自のエラーハンドリングを行う
E.g., if error.cause().is::<Timeout>() { .. } or if error.is::<Timeout>()
呼び出し元が処理すべきエラーの種類に関しては、書くメソッドのドキュメントのErrors節に記述する
もしもっと強くハンドリングを望む場合には、そもそもResultのErr部ではなくOk部にenumを使って表現するようにする
E.g., enum MaybeTimeout<T> { Ok(T), Timeout }; fn foo() -> Result<MaybeTimeout<bool>, Error> {}
causeには基本的には何でも渡せるようにする?
trackableも何らかの形でサポートしたい(互換性維持や移行用)
代替案
track!的なマクロの提供は、track_failureとか適当なcrateを用意して、そこでmacroを定義すればいけそう
色々不要な機能は付いているけれど、それらは使わなければ良いだけ?
Clone (と内部のArc)は欲しい
trackableとの互換性も欲しい
=> failureの安定版もまだなので、しばらくは独自crateで良さそう?
上記方法の問題点
ノード間通信を挟んだら、型情報が失われてしまってハンドリングが面倒になりそう
trackableならトップレベルのErrorKind(典型的にはenum)の情報は保持されているので、基本的にはこれによる問題は発生しない
ノード間通信が発生しない場合には共通の Error型を使って、発生する場合にだけ独自のenum式のErrorが定義できると良さそう?