thiserror crate の基本的な使い方
前回は Error
の downcast
について書きました 。今回は thiserror
crate の基本的な使い方について書きます。
thiserror
crate とは
thiserror
は std::error::Error
trait の実装のための derive macro を提供する crate です。
thiserror
は Rust におけるエラー関連の定番 crate のひとつだと思います。
std::error::Error
trait については過去に書きました。
derive macro についてはそのうち書きます(たぶん)。
インストール方法
cargo add thiserror
くらい。特に変わったところはないです。
使い方: unit struct の例
#[derive(Debug, thiserror::Error)]
#[error("unit struct error")]
struct UnitStructError;
fn assert_impls<T: std::fmt::Debug + std::fmt::Display + std::error::Error>() {}
assert_impls::<UnitStructError>();
assert_eq!(UnitStructError.to_string(), "unit struct error");
thiserror::Error
を #[derive(...)]
に指定します。
#[error("...")]
という属性の指定は Display
の format になります。
これで std::error::Error
と std::fmt::Display
が実装されます。
std::error::Error
は Debug + Display
を要求するので #[derive(...)]
には Debug
も指定します。
#[from]
+ {0}
の例
使い方: tuple struct + #[derive(Debug, thiserror::Error)]
#[error("tuple struct error: {0}")]
struct TupleStructError(#[from] std::io::Error);
assert_eq!(
format!("{}", TupleStructError(std::io::Error::other("error1"))),
"tuple struct error: error1"
);
assert_eq!(
format!(
"{}",
// `#[from]` は `impl From<std::io::Error> for TupleStructError` を提供する
TupleStructError::from(std::io::Error::other("error2"))
),
"tuple struct error: error2"
);
assert_eq!(
format!(
"{:?}",
// `#[from]` のフィールドは `std::error::Error` trait の `source()` にも使用される
TupleStructError(std::io::Error::other("error3")).source()
),
"Some(Custom { kind: Other, error: \"error3\" })"
);
今度は tuple struct 。エラーの理由を残したいことも多いのでこの形もままあると思います。
今回は #[from]
属性も指定しています。
これは From
trait の実装を提供してくれます。これで ?
operator を使いやすくなりますね。
?
operator も From
trait も過去に書いてないですね、そのうち書きます(たぶん)。
#[from]
は Error
trait の source()
にも自動で使われます。
あとは #[error("...")]
に tuple の field を指定しています。 {0}
の箇所がそうです。
format!
macro と似たような形なので違和感なく使えると思います。
#[source]
+ {var:?}
の例
使い方: named fields struct + #[derive(Debug, thiserror::Error)]
#[error("struct error: {cause:?}")]
struct StructError {
#[source]
cause: std::io::Error,
// `#[source]` の代わりに↓のようなフィールド名 `source` でも良い
// source: std::io::Error,
}
assert_eq!(
format!(
"{}",
StructError {
cause: std::io::Error::other("error1")
}
),
"struct error: Custom { kind: Other, error: \"error1\" }"
);
// `#[source]` は `From` trait を実装しないので、↓はコンパイルエラーになる
// StructError::from(std::io::Error::other("error2"))
assert_eq!(
format!(
"{:?}",
// `#[source]` のフィールドは `std::error::Error` trait の `source()` にも使用される
StructError {
cause: std::io::Error::other("error2")
}
.source()
),
"Some(Custom { kind: Other, error: \"error2\" })"
);
今度は named field struct 。
フィールド名があるときの変数は {var}
の形で書きます。
さらに、ここでは {var:?}
を使用しています。これは Debug
出力を使用します。
tuple struct のときの {0}
も同様に {0:?}
のように書いて Debug
出力を使用できます。
#[source]
属性を指定すると、そのフィールドが source()
に使用されます。
#[from]
と異なり、 From
trait の実装は提供されません。これは #[from]
の型が衝突してしまうと From
trait を実装できなくなるので、それを回避するのに使ったりします。
#[error(transparent)]
の例
使い方: enum + #[derive(Debug, thiserror::Error)]
enum EnumError {
#[error("io error")]
Io(#[source] std::io::Error),
#[error(transparent)]
From(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
Unknown(Box<dyn std::error::Error + Send + Sync>),
}
assert_eq!(
format!("{}", EnumError::Io(std::io::Error::other("error1"))),
"io error"
);
assert_eq!(
format!(
"{}",
EnumError::from(Box::<dyn std::error::Error + Send + Sync>::from("error2"))
),
"error2"
);
assert_eq!(
format!(
"{:?}",
EnumError::from(Box::<dyn std::error::Error + Send + Sync>::from("error2")).source()
),
"None"
);
assert_eq!(format!("{}", EnumError::Unknown("error3".into())), "error3");
enum の例です。 variant ごとに #[error("...")]
などを指定できます。
#[error(transparent)]
は内部のフィールドのものを透過的に使用します。
この例だと Box<dyn std::error::Error + Send + Sync>
として "error3"
を持っているので、それを to_string()
すると、そのまま "error3"
と表示されます。
transparent
なので、 EnumError
の中に Box<...>
を持っているように見せるのではなく Box<...>
かのように見せるので #[source]
は使用できません。 transparent variant can't contain #[source]
というエラーになります。
一方で transparent
のとき、 #[from]
は使用できます。ただし、 source()
としては提供されず、 From
trait の実装のみ提供されます。
通常は #[from]
が #[source]
を兼ねている (しかも同時に指定できない) ので、すこしややこしいですね。
次回予告
#[error("...", var = ...)]
のような追加の format! の引数や #[backtrace]
については書いていませんが、thiserror の基本的な使い方は書けたと思います。
これらの内容は https://github.com/dtolnay/thiserror のリポジトリの README にもっと簡潔かつ漏れなくまとめられているので、そちらを読むのをおすすめします。
次回は cargo-expand で実際に展開されたマクロを見てみたいと思います。
Discussion