目次
- thiserrorとは
- 何が嬉しいの?
- thiserrorの共通の使い方
- 他のパッケージのエラーに紐づくエラーの定義方法
- 一つの引数を受け付けるエラーの定義方法
- 構造体の引数を受け付けるエラーの定義方法
- 引数を受け付けないエラーの定義方法
- matchを使う場合
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の中で引数に入れてあげることが可能になります。