1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustでthiserrorを使ってエラーの変換を楽に管理する

Last updated at Posted at 2024-06-25

目次

thiserrorとは

ライブラリのエラーを含む独自のエラーを作成したい場合に便利なクレートです。
Rustではエラーの受け渡しが大変ですが、thiserrorを使用すれば、簡単に様々なエラーを統合することができます。
ライブラリのエラーを含まない場合は同じ作者のanyhowが選択肢になると思います。
Github: https://github.com/dtolnay/thiserror

何が嬉しいの?

RustでResultを処理する時にエラーのEnumが違うと、エラーを置き換える処理を記載する必要があります。
その時にthiserrorをちゃんと設定しておくと、?をつけておくだけで適切にエラーが処理されます。
特に嬉しいのがライブラリのエラーも設定に基づいて置き換えてくれる点です。詳細は他のパッケージのエラーに紐づくエラーの定義方法を参照ください。

thiserrorの共通の使い方

共通の使い方をまず説明します。
GithubのREADME.mdを元に説明します。

use thiserror::Error;

pub enum MyError {
    #[error("unknown data store error")]
    Unknown
}

それぞれを説明します。

#[error("Failed SQL execution")]

errorは人間が読めるエラーメッセージを指定するためのアトリビュートになります。
設定は必須です。""の中に返却するエラーメッセージを記載します。

Unknown

UnknownはEnumの要素の名前になります。

他のパッケージのエラーに紐づくエラーの定義方法

一番重要だと思う使い方です。

#[error("data store disconnected")]
Disconnect(#[from] io::Error)

[from]で定義したio::Errorが発生した時のエラーはDisconnectエラーとして定義されます。
これで、io::Errorで定義されている何かしらのエラーが発生した場合は全てDisconnectにラップされます。

そして、便利なことに作成したErrorのEnum、今回でいうとMyErrorですがこちらに定義していないエラーが返ってくる場合、`?` couldn't convert the error to `MyError`といったエラーが出ます。なので、抜け漏れのチェックまで実施してくれるという便利さになっています。

#[error(transparent)]
Disconnect(#[from] io::Error)

上記のようにエラーメッセージの代わりにtransparentと入れると、rusqlite::Errorが返してくれるエラーメッセージをそのまま表示してくれます。

一つの引数を受け付けるエラーの定義方法

pub enum MyError {
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
}

エラーを呼び出す際に{0}で指定した箇所に好きな文字列を入れることができます。

Err(MyError::Redaction("hogehoge".to_string()));

と入れると、the data for key `hogehoge` is not availableと返ってきます。
これは複数入れることも可能です。

pub enum MyError {
    #[error("the data for key `{0}` `{1}` is not available")]
    Redaction(String),
}
Err(MyError::Redaction("hogehoge".to_string(), "hugahuga".to_string()));

構造体の引数を受け付けるエラーの定義方法

上で説明したものの構造体版です。

定義方法

#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader { expected: String, found: String },

呼び出し方法

Err(MyError::InvalidHeader {
        expected: "hogehoge".to_string(),
        found: "hugahuga".to_string(),
});

返ってくる値

invalid header (expected "aaaaaaa", found "bbbbbbbb")

引数を受け付けないエラーの定義方法

上で説明したものの引数がない版です。

定義方法

#[error("unknown data store error")]
Unknown,

呼び出し方法

Err(MyError::Unknown);

返ってくる値

unknown data store error

matchを使う場合

matchを使用して、パターンを分けることができます。
それをして、パッケージのどのエラーが呼ばれたかを知ることが出来たり、別のエラーに変換することが出来ます。gRPCのような返却するエラーが決まっている場合などはmatchを使用して別のエラーに変換してあげる必要があるのかと思います。
ちょっと上記の例と変わりますが、rusqliteを使用した場合は下記のように書き換えが出来ます。

#[derive(Error, Debug)]
enum MyError {
    #[error("Failed SQL execution!!")]
    SQLiteError(#[from] rusqlite::Error),

    #[error("unknown error")]
    Unknown,
}
match result {
    Ok(_) => println!("Ok"),
    Err(e) => match e {
        MyError::SQLiteError(rusqlite::Error::SqliteSingleThreadedMode) => {
            println!("SqliteSingleThreadedMode error occurred");
        }
        MyError::SQLiteError(rusqlite::Error::SqliteFailure(err, Some(msg))) => {
            println!("Sqlite failure: {}, message: {}", err, msg);
        }
        MyError::Unknown => {
            println!("Unknown error occurred")
        }
        _ => println!("Other error occurred"),
    },
}

SqliteFailureはSqliteFailure(ffi::Error, Option<String>)と定義されているため、matchの中で引数に入れてあげることが可能になります。

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?