LoginSignup
20

More than 5 years have passed since last update.

RFC 2504 "fix_error": Rustの新たなErrorトレイト

Last updated at Posted at 2019-03-09

この記事はRustのRFC2504 "fix_error"の和訳です。現在はRFCが採用され、nightlyに実装が進んでいる状態です(なのでまだ動く環境はない)。

途中で登場する#[non_exhaustive]についてはこちら:Non-exhaustive enum/struct

以下翻訳


Summary

std::error::Errorトレイトを使いやすくするために変更します。backtraceモジュールを標準ライブラリに追加し、バックトレースにアクセスするための標準的なインターフェースを導入します

Motivation

Errorトレイトは長い間、いくつかの点で欠陥が指摘されていました:

  1. descriptionメソッドの機能が限られており、通常静的な文字列を返すだけでエラー型のDisplayの実装のために十分ではありませんでした
  2. causeメソッドの定義が、ユーザーに原因の型へダウンキャストを不必要に妨げています
  3. 標準となるバックトレース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つから構成されます:

  1. Display, Debugの実装をエラーメッセージを表示するために用います。理想的にはDisplayエンドユーザーに向けたメッセージに、Debugはプログラマに向けたメッセージになります。
  2. backtraceメソッドが追加されます。エラーがバックトレースを保持している場合、このメソッドを通して取得できるはずです。エラー型がバックトレースを保持する事は必須ではありません。
  3. 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_BACKTRACERUST_LIB_BACKTRACEが追加され、それぞれ独立にRUST_BACKTRACEより優先されます。

移行プラン

causedescriptionの非推奨への変更、及びbacktracesourceメソッドの追加は後方互換があります。これらの変更は(訳注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

既存のcauseAPIの問題は返されるエラー型が'staticでない事でした。健全性の理由から'staticでないトレイトオブジェクトはダウンキャストが出来ないので、これではエラー型のトレイトオブジェクトがダウンキャストできません。

バックトレースに関する補足

バックトレースの挙動はプラットフォームにいくらか依存しており、一部のプラットフォームではバックトレースは不正確な情報を含んでいることがあります。
プラットフォームに応じて、標準ライブラリによって提供されるバックトレースはユーザーに表示することを目的としており、プログラムの状態に対する完全な表現を保証しません。

How this impacts failure

failure crateはFailトレイトによく似た(しかし正確には一致しない)APIを定義しています。
failureにおける破壊的変更になりますが、FailErrorを拡張するトレイトになるでしょう。

// 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の実験での経験を基にしている。主要な結論としては:

  1. 現在のErrorトレイトには重大な欠陥がある
  2. failureのFailトレイトはより良いAPIになっている
  3. 複数のエラートレイトがあるのはエコシステムにとって混乱のもとである

このRFCはエラートレイトの問題と新たなトレイトの導入を上手く解決するものになる

Unresolved questions

Backtrace API

このRFCでは意図的に最小のAPIを提案しており、将来的に多くの拡張を考えています。主要な例としては:

  1. プログラミング可能なバックトレースのイテレーションに対するバックトレースAPIの拡張
  2. 標準ライブラリでのDisplayErrorに対するderive
  3. Errorfailureで試験されている機能、例えばcause iteratorに対する補助メソッドを提供する

これらはこのRFCには含まれないが、将来のRFCで議論されるでしょう。

さらに言えば、backtraceの内部的なnullabilityの設計は間違いと分かるかもしれない:
バックトレースAPIがnightlyのみで使える実装期間に得た経験によっては再びOption<Backtrace>に戻すかもしれない。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20