💬
thiserrorでカスタムエラーを実装する
Rustでライブラリなどを作成していくと、エラー処理のために独自のエラー型を作ることがあります。
例えば下記コードのように、Vecが空のときに返されるOption型のNoneを独自のエラー型に変換したり、ParseIntErrorをラップして、独自の型としてエラーメッセージなどに追加の情報を足したりできます。
main.rs
use std::error::Error as StdError;
use std::fmt;
use std::num::ParseIntError;
type Result<T> = std::result::Result<T, CustomError>;
#[derive(Debug)]
enum CustomError {
EmptyVec,
Parse(ParseIntError),
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CustomError::EmptyVec => write!(f, "list not empty"),
CustomError::Parse(_) => write!(f, "invalid list element"),
}
}
}
impl StdError for CustomError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
CustomError::EmptyVec => None,
CustomError::Parse(ref e) => Some(e),
}
}
}
impl From<ParseIntError> for CustomError {
fn from(err: ParseIntError) -> CustomError {
CustomError::Parse(err)
}
}
fn first_element(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(CustomError::EmptyVec)?;
let element = first.parse::<i32>()?;
Ok(element)
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first element is {}", n),
Err(e) => {
println!("Error: {}", e);
if let Some(source) = e.source() {
println!(" Caused by: {}", source);
}
}
}
}
fn main() {
let numbers = vec!["1", "2", "3"];
let empty = vec![];
let strings = vec!["string", "2", "3"];
print(first_element(numbers));
print(first_element(empty));
print(first_element(strings));
}
上記コードを実行すると下記のように、表示されます。
$ cargo run
The first element is 1
Error: list not empty
Error: invalid list element
Caused by: invalid digit found in string
ただし、ライブラリがどんどん大きくなってきて、エラー型が増えていくとそれにともない、コード量も多くなっていき、見通しが悪くなっていきます。
そこでthiserror crateを使い、これらのコードの記載を簡略化していきます。
thiserrorでエラー処理を実装する
thiserrorは標準ライブラリーのstd::error::Errorトレイを実装するための便利なマクロを提供します。
cargo.tomlにthiserrorを記載して、上記コードを修正します。
Cargo.toml
[dependencies]
thiserror= "1"
main.rs
use std::error::Error as StdError;
-use std::fmt;
use std::num::ParseIntError;
+use thiserror::Error;
type Result<T> = std::result::Result<T, CustomError>;
- #[derive(Debug)]
+ #[derive(Debug, Error)]
enum CustomError {
+ #[error("list not empty")]
EmptyVec,
+ #[error("invalid list element")]
- Parse(ParseIntError),
+ Parse(#[from] ParseIntError),
}
-
- impl fmt::Display for CustomError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- CustomError::EmptyVec => write!(f, "list not empty"),
- CustomError::Parse(_) => write!(f, "invalid list element"),
- }
- }
- }
-
- impl StdError for CustomError {
- fn source(&self) -> Option<&(dyn StdError + 'static)> {
- match *self {
- CustomError::EmptyVec => None,
- CustomError::Parse(ref e) => Some(e),
- }
- }
- }
-
- impl From<ParseIntError> for CustomError {
- fn from(err: ParseIntError) -> CustomError {
- CustomError::Parse(err)
- }
- }
fn first_element(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(CustomError::EmptyVec)?;
let element = first.parse::<i32>()?;
Ok(element)
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first element is {}", n),
Err(e) => {
println!("Error: {}", e);
if let Some(source) = e.source() {
println!(" Caused by: {}", source);
}
}
}
}
fn main() {
let numbers = vec!["1", "2", "3"];
let empty = vec![];
let strings = vec!["string", "2", "3"];
print(first_element(numbers));
print(first_element(empty));
print(first_element(strings));
}
このように記載することで最初のコードと同じように出力できます。
$ cargo run
The first element is 1
Error: list not empty
Error: invalid list element
Caused by: invalid digit found in string
thiserrorによりDisplay, Error, From implの実装を自動でおこなってくれます。
簡単に説明すると、#[error("..")]
で()
内のメッセージを記載すると、Display implを実装してメッセージが表示してくれます。
また#[error("{var}")]
ように書くとDisplay impl内のwrite!("{}", self.var)
のように動的にメッセージを表示することもできます。
さらに#[from]
でFrom implとsource()
の実装を自動でしてくれます。
その他にも機能やコードがあるので興味がある方はthiserrorのDocumentを参照してみてください。
Discussion