ys memos

Blog

rustで最強のCustomResult,CustomErrorを定義する方法


rust

2023/08/31


Rustにおいて、 ?によるエラー処理は非常に便利である。

明示的で簡潔な記法で他の言語と同様なエラー処理を行いつつ、安全にエラーハンドリングを行うことが可能という点が特に気に入っている。

しかし、そのまま使うだけでは、同じ型のエラーしか ?によって処理することはできない。

そこで、自分の受け取りたいあらゆるエラー型を受け取れるCustomError、およびそれを楽に利用できるCustomResultを定義する方法を紹介する。


thiserrorというクレートを使えば万事解決。

(公式ドキュメントを読めば本記事以上に使えます!)

このクレートは非常に便利である。カスタムエラーを簡単に定義できるし、他のエラー型からの変換も簡潔に設定できる。


サンプルとして、よく使われるであろう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にあるので、必要な場合は見てみてください。


関連タグを探す