2023/08/31
はじめに
Rustにおいて、 ?
によるエラー処理は非常に便利である。
明示的で簡潔な記法で他の言語と同様なエラー処理を行いつつ、安全にエラーハンドリングを行うことが可能という点が特に気に入っている。
しかし、そのまま使うだけでは、同じ型のエラーしか ?
によって処理することはできない。
そこで、自分の受け取りたいあらゆるエラー型を受け取れるCustomError、およびそれを楽に利用できるCustomResultを定義する方法を紹介する。
基本
thiserrorというクレートを使えば万事解決。
(公式ドキュメントを読めば本記事以上に使えます!)
このクレートは非常に便利である。カスタムエラーを簡単に定義できるし、他のエラー型からの変換も簡潔に設定できる。
CustomError, CustomResultの定義例
サンプルとして、よく使われるであろうio:Error
を受け取れるようにした。
use std::io;
use thiserror::Error;
pub type CustomResult<T> = Result<T, CustomError>;
#[derive(Debug, Error)]
pub enum CustomError {
#[error("I/O error occurred: {0}")]
IoError(#[from] io::Error),
#[error("Error occurred on hoge()")]
HogeError,
#[error("undefined error")]
UnknownError,
#[error("Undefined error occurred: {0}")]
Undefined(String),
}
説明
まずは必要なクレートのuse
use std::io;
use thiserror::Error;
これはカスタムなResultで、Result
の型エイリアスである。
Errorとして、CustomError
を使うので、このCustomResult
型も万能になっていく。
これにより、どのエラーも?
で処理できるようになる。
関数定義時は、fn fuga() -> CustomResult<i8>
みたいにする。
pub type CustomResult<T> = Result<T, CustomError>;
本題のCustomError
の定義。Debug
は無くてもいいが、Error
は必須。
#[derive(Debug, Error)]
pub enum CustomError {
まずはio::Error
への対応。
#[from]
で元となるエラーを書くことができ、そのフィールドを#[error()]
内で利用できる。
この0
というのは、タプル型の1つ目の要素という意で、CustomError::IoError.0
(疑似コード)を指す。
#[error("I/O error occurred: {0}")]
IoError(#[from] io::Error),
カスタマイズしたエラー。これは「hoge
が失敗した」という意図のエラーを書いてみた。
#[error("Error occurred on hoge()")]
HogeError,
これはちょっとしたコツなのだが、開発段階で必要になる全てのエラーを追加していくのは骨が折れるし、不要になれば削除されることもあると予想される。
そのため、一時的に利用できるエラーを書いておくと、実際の開発が円滑になると考えている。
開発時の利便性のためにメッセージをつけられるようにすると、どんなエラーが発生したのかの追跡性が向上する。
#[error("Undefined error occurred: {0}")]
Undefined(String),
}
おわりに
自分は、このようなカスタマイズによって、元々便利なエラーハンドリングをもっと便利に使えるようになりました!
エラーを返す際に便利な様、タプル型を使うのが基本だと思います。ですが、必要があれば構造体で定義してもいいと思います。その例は公式ドキュメントのExampleにあるので、必要な場合は見てみてください。