web-dev-qa-db-ja.com

Rustでカスタムの `Error`タイプをどのように定義しますか?

私はいくつかの異なるエラーのいくつかを返す可能性のある関数を書いています。

fn foo(...) -> Result<..., MyError> {}

このようなエラーを表すには、おそらく独自のエラータイプを定義する必要があります。私はそれがenumの可能性のあるエラーであり、いくつかのenumバリアントに診断データが添付されていると推測しています:

enum MyError {
    GizmoError,
    WidgetNotFoundError(widget_name: String)
}

それが最も慣用的な方法ですか?そして、どのようにしてError特性を実装しますか?

31
Jo Liss

Error とまったく同じように実装します 他の特性 ;それについて特別なことは何もありません:

pub trait Error: Debug + Display {
    fn description(&self) -> &str { /* ... */ }
    fn cause(&self) -> Option<&Error> { /* ... */ }
    fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}

descriptioncause、およびsourceにはすべてデフォルトの実装があります1、およびあなたの型はDebugDisplayも実装する必要があります。これらはスーパートレイトです。

use std::{error::Error, fmt};

#[derive(Debug)]
struct Thing;

impl Error for Thing {}

impl fmt::Display for Thing {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Oh no, something bad went down")
    }
}

もちろん、Thingに含まれる内容、つまりメソッドの実装は、どの種類のエラーが発生したいかに大きく依存します。おそらく、そこにファイル名を含めるか、何らかの種類の整数を含める必要があります。おそらく、複数のタイプのエラーを表すために、enumの代わりにstructが必要です。

既存のエラーをラップする場合は、Fromを実装してこれらのエラーとエラーを変換することをお勧めします。これにより、try!および?と人間工学に基づいた解決策があります。

それが最も慣用的な方法ですか?

慣例的に、ライブラリには少数(おそらく1〜3)の主要なエラータイプが公開されると思います。これらは、他のエラータイプの列挙である可能性があります。これにより、クレートの消費者はタイプの爆発に対処できなくなります。もちろん、これはあなたのAPIと、いくつかのエラーをひとまとめにすることが理にかなっているかどうかに依存します。

もう1つ注意すべきことは、エラーにデータを埋め込むことを選択すると、広範囲に及ぶ結果になる可能性があることです。たとえば、標準ライブラリにはファイル関連のエラーにファイル名が含まれていません。これを行うと、すべてのファイルエラーにオーバーヘッドが追加されます。通常、メソッドの呼び出し元には関連するコンテキストがあり、そのコンテキストをエラーに追加する必要があるかどうかを決定できます。


これを何回か手作業で行い、すべてのピースがどのように組み合わされるかを確認することをお勧めします。一度それを手に入れると、手動でやるのに飽きてきます。次に、ボイラープレートを削減するマクロを提供するクレートをチェックアウトできます。

私が好むライブラリはSNAFU(私が書いたため))、それを元のエラータイプで使用する例を以下に示します:

// This example uses the simpler syntax supported in Rust 1.34
use snafu::Snafu; // 0.2.0

#[derive(Debug, Snafu)]
enum MyError {
    #[snafu(display("Refrob the Gizmo"))]
    Gizmo,
    #[snafu(display("The widget '{}' could not be found", widget_name))]
    WidgetNotFound { widget_name: String }
}

fn foo() -> Result<(), MyError> {
    WidgetNotFound { widget_name: "Quux" }.fail()
}

fn main() {
    if let Err(e) = foo() {
        println!("{}", e);
        // The widget 'Quux' could not be found
    }
}

注各列挙値の冗長なErrorサフィックスを削除しました。また、タイプErrorを呼び出して、コンシューマーがタイプのプレフィックス(mycrate::Error)またはインポート時に名前を変更(use mycrate::Error as FooError)。


1 RFC 2504 が実装される前は、descriptionが必須のメソッドでした。

39
Shepmaster

それが最も慣用的な方法ですか?そして、どのようにエラー特性を実装しますか?

はい、それは一般的な方法です。 「イディオマティック」は、エラーをどの程度強く入力したいか、および他のエラーと相互運用する方法によって異なります。

そして、どのようにエラー特性を実装しますか?

厳密に言えば、ここにいる必要はありません。 Errorを必要とする他のものとの相互運用性のために、戻り値の型をこの列挙型として直接定義しているため、コードはそれなしでも動作するはずです。

2
Steve Klabnik

クレート custom_error を使用すると、上記で提案したものよりも定型的なカスタムエラータイプを定義できます。

custom_error!{MyError
     Io{source: io::Error}             = "input/output error",
     WidgetNotFoundError{name: String} = "could not find widget '{name}'",
     GizmoError                        = "A gizmo error occurred!"
}

免責事項:私はこのクレートの著者です。

1
lovasoa