この記事はRustのRFC2504 "fix_error"の和訳です。現在はRFCが採用され、nightlyに実装が進んでいる状態です(なのでまだ動く環境はない)。
途中で登場する#[non_exhaustive]
についてはこちら:Non-exhaustive enum/struct
以下翻訳
- Feature Name:
fix_error
- Start Date: 2018-07-18
- RFC PR: rust-lang/rfcs#2504
- Rust Issue: rust-lang/rust#53487
Summary
std::error::Error
トレイトを使いやすくするために変更します。backtrace
モジュールを標準ライブラリに追加し、バックトレースにアクセスするための標準的なインターフェースを導入します
Motivation
Error
トレイトは長い間、いくつかの点で欠陥が指摘されていました:
-
description
メソッドの機能が限られており、通常静的な文字列を返すだけでエラー型のDisplay
の実装のために十分ではありませんでした -
cause
メソッドの定義が、ユーザーに原因の型へダウンキャストを不必要に妨げています - 標準となるバックトレースAPIをユーザーに提供していません
このRFCではError
のこれらのメソッドを非推奨(deprecated)とし、新たに二つのメソッドを追加します。その結果Error
トレイトは次のようになります:
trait Error: Display + Debug {
fn backtrace(&self) -> Option<&Backtrace> {
None
}
fn source(&self) -> Option<&dyn Error + 'static> {
None
}
}
Guide-level explanation
Errorトレイトの新たなAPI
新しいAPIは主に次の3つから構成されます:
-
Display
,Debug
の実装をエラーメッセージを表示するために用います。理想的にはDisplay
はエンドユーザーに向けたメッセージに、Debug
はプログラマに向けたメッセージになります。 -
backtrace
メソッドが追加されます。エラーがバックトレースを保持している場合、このメソッドを通して取得できるはずです。エラー型がバックトレースを保持する事は必須ではありません。 -
source
メソッドが追加されます。これはエラーの原因となった別のエラー型を返します。もしエラーが原因となった他のエラーを持たない場合このメソッドはNone
を返します。
The backtrace API
このRFCによってbacktrace
モジュールが新たに標準ライブラリに追加されます:
pub struct Backtrace {
// ...
}
impl Backtrace {
// プラットフォームがサポートされている場合、現在のスタックのバックトレースを取得します
//
// この関数は環境変数の値を参照します
pub fn capture() -> Backtrace {
// ...
}
// プラットフォームがサポートされている場合、現在のスタックのバックトレースを取得します
//
// この関数は環境変数の値を無視します
pub fn force_capture() -> Backtrace {
// ...
}
pub fn status(&self) -> BacktraceStatus {
// ...
}
}
impl Display for Backtrace {
// ...
}
impl Debug for Backtrace {
// ...
}
#[non_exhaustive]
pub enum BacktraceStatus {
Unsupported,
Disabled,
Captured
}
この最小の初期APIはエンドユーザーにバックトレースを表示することを目的としてます。そのうちバックトレースの個々のフレームを表示できたりするようになるでしょう。
バックトレースを制御する環境変数
現行RUST_BACKTRACE
環境変数がパニック時のバックトレースの生成を制御していますが、このRFC後は標準ライブラリでのバックトレースの生成も制御するようになります:Backtrace::capture
はこの環境変数がセットされていない限りバックトレースを生成しませんが、Backtrace::force_capture
はこの環境変数を無視します。
加えてパニック時と標準ライブラリ経由の場合を独立に制御するために二つの変数、RUST_PANIC_BACKTRACE
とRUST_LIB_BACKTRACE
が追加され、それぞれ独立にRUST_BACKTRACE
より優先されます。
移行プラン
cause
とdescription
の非推奨への変更、及びbacktrace
とsource
メソッドの追加は後方互換があります。これらの変更は(訳注Rust2018のように)儀式ばらずに実行でき、Error
トレイトはより機能的になります。
cause
のデフォルト実装は、このメソッドは非推奨になりますが、次のように変更されます:
fn cause(&self) -> Option<&dyn Error> {
self.source()
}
これにより、もしエラー型がsource
を定義しているのに誰かが非推奨のcause
を呼び出したとしても正しく原因となったエラー型を返すことが出来ます。ただし元の型にダウンキャストすることはできません。
安定化
source
の追加とcause
の非推奨化はRFCの実装直後の安定化されます。
backtrace
メソッドの追加とバックトレースAPIの追加はbacktrace
featureとしてしばらくunstableになるでしょう。
Reference-level explanation
Why cause
-> source
既存のcause
APIの問題は返されるエラー型が'static
でない事でした。健全性の理由から'static
でないトレイトオブジェクトはダウンキャストが出来ないので、これではエラー型のトレイトオブジェクトがダウンキャストできません。
バックトレースに関する補足
バックトレースの挙動はプラットフォームにいくらか依存しており、一部のプラットフォームではバックトレースは不正確な情報を含んでいることがあります。
プラットフォームに応じて、標準ライブラリによって提供されるバックトレースはユーザーに表示することを目的としており、プログラムの状態に対する完全な表現を保証しません。
How this impacts failure
failure
crateはFail
トレイトによく似た(しかし正確には一致しない)APIを定義しています。
failure
における破壊的変更になりますが、Fail
はError
を拡張するトレイトになるでしょう。
// Maybe rename to ErrorExt?
trait Fail: Error + Send + Sync + 'static {
// various provided helper methods
}
impl<E: Error + Send + Sync + 'static> Fail for E {
}
Fail
のderiveを提供する代わりに、failure
は標準ライブラリのError
に対する次のようなderiveを提供することになるでしょう:
#[derive(Debug, Display, Error)]
#[display = "My display message."]
struct MyError {
#[error(source)]
underlying: io::Error,
backtrace: Backtrace,
}
failure
の新しいAPIの詳細はfailure
のメンテナによって決定されるでしょうが、このRFCによってfailure
が追放されるわけではありません。
ここではfailure
が標準ライブラリのError
と協働できることを示しているにすぎません。
Drawbacks
既に非推奨のメソッドを使用しているユーザーに対して(コンパイラの警告で)移行を推奨し、ライブラリの作者に対して再び新たなメソッドをオーバーライドするかどうかの判断をせまることになる
Rationale and alternatives
Provide a new error trait
明らかな別案として、全く新しいトレイトを導入する案がある。これはこのRFCで導入される方法よりも深い変更が必要となる。
例えばfailure
が採用しているのと似た方法をとり、より強い制約を全ての実装に対して課す:
trait Fail: Display + Debug + Send + Sync + 'static {
fn cause(&self) -> Option<&dyn Fail> {
None
}
fn backtrace(&self) -> Option<&Backtrace> {
None
}
}
これで既存のトレイトとの後方互換性に縛られることなく完全なエラートレイトを実装することが可能となるだろう。
しかしながら、これはエコシステムにとって既存のトレイトを変更するよりもはるかに破壊的なものになる。
既にfailure
と(例えばserde
のように)Error
を受け取るAPIとの不整合に遭遇しており、現行のトレイトとの不整合を解消するほんの少しの改善の為に今この変更をとる価値はないと判断した。
もし将来、エラートレイトの問題点をこのRFCが十分に解決できないとわかったらこの代替案に切り替えるでしょう。
Bikeshedding the name of source
cause
の他の名称として著者が思いつくのはorigin
である。
Prior art
この提案は既存のErrorトレイトのAPIとfailure
の実験での経験を基にしている。主要な結論としては:
- 現在の
Error
トレイトには重大な欠陥がある - failureの
Fail
トレイトはより良いAPIになっている - 複数のエラートレイトがあるのはエコシステムにとって混乱のもとである
このRFCはエラートレイトの問題と新たなトレイトの導入を上手く解決するものになる
Unresolved questions
Backtrace API
このRFCでは意図的に最小のAPIを提案しており、将来的に多くの拡張を考えています。主要な例としては:
- プログラミング可能なバックトレースのイテレーションに対するバックトレースAPIの拡張
- 標準ライブラリでの
Display
とError
に対するderive -
Error
にfailure
で試験されている機能、例えばcause iterator
に対する補助メソッドを提供する
これらはこのRFCには含まれないが、将来のRFCで議論されるでしょう。
さらに言えば、backtrace
の内部的なnullabilityの設計は間違いと分かるかもしれない:
バックトレースAPIがnightlyのみで使える実装期間に得た経験によっては再びOption<Backtrace>
に戻すかもしれない。