5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustでthiserrorを使ってエラーハンドリングをする

Last updated at Posted at 2025-12-13

この記事はRust+SvelteKit+CDKでRSS要約アプリを作ってみる Advent Calendar 2025の14日目の記事になります。

また、筆者が属している株式会社野村総合研究所のアドベントカレンダーもあるので、ぜひ購読ください。

はじめに

RustはTypeScriptと違って、言語レベルでResultOptionを提供しており、エラーハンドリングを堅牢に実装することができます。Resultは、

  • 成功時の値
  • 失敗時のError

の二つを内包しています。TypeScriptでは(デフォルトでは)何かエラーが発生した場合は例外を投げることで表現しますが、RustではResultErrorを詰めることで表現します。この機構は非常に便利で安全なのですが、処理によっては複数のエラーパターンをまとめた独自のErrorを定義したいときがあります。そのようなケースに便利なのがthiserrorクレートです。

thiserrorクレートの特徴

thiserrorクレートは、独自のエラー型(enum)を簡単に定義できるマクロを提供します。関数やライブラリ単位で発生しうるエラーに名前をつけて管理したいときに便利です。呼び出す側は、その独自型を参照することでどんなエラーが発生したかを判別して分岐処理しやすいため、エラーハンドリングを堅牢に実装することができます。

thiserrorによる独自エラー定義

cdk/lambda/src/lib.rsの実装を見てみます。このファイルではRSSやAWSリソースの取得処理を行っており、関数ごと、もしくはリソース単位のRepositoryごとに独自エラーを定義しています。例えばRSSフィード周りの処理で発生しうるエラーとしてFetchErrorを定義しています。

#[derive(Debug, thiserror::Error)]
pub enum FetchError {
    #[error("HTTP request error: {0}")]
    HttpRequest(#[from] reqwest::Error),
    #[error("RSS parsing error: {0}")]
    RssParsing(#[from] ParseFeedError),
}

thiserrorクレートで独自エラーを定義する場合は、deriveマクロを用いてthiserror::Errorトレイトを実装します。その上で、各フィールドにerror属性で各エラーパターンを定義します。error属性を使うと、エラーを表示した時の文字列を指定でき、自動的にDisplayトレイトを実装します。

各エラーの構造は自由に定義できますが、他の独自のエラーから派生させることもできます。今回はreqwestfeed-rsのエラーをそのまま使用するため、#[from]属性を使っています。#[from]属性を使うと他のエラーを独自エラー(ここではFetchError)に変換することができます。

#[from]の仕組み

#[from]属性は、Fromトレイトを自動的に実装するマクロです。FromトレイトはRustの標準ライブラリに組み込まれているトレイトで、ある型から別の型への変換を表すトレイトです。ある型Tに対して変換前の型Uに対応したFromトレイトを実装していると、UからTへの変換が可能になります。サードパーティで発生したエラーを自前のエラーに変換するためには、このFromトレイトを実装する必要がありますが、#[from]属性を使うと、この実装を自動で行なってくれるようになります。上記の例では、reqwest::ErrorからFetchError::HttpRequestへの変換を自動で行なってくれます。

Fromトレイトの実装とは、具体的には型変換を行うfromメソッドの実装を指します。ある型Tに対して変換前の型Uに対応したFromトレイトを実装しているとして、実際に変換を行う場合にはfromメソッドを呼び出すことになります。

暗黙的なエラーの変換

実際にエラーが起きうる処理を呼び出す際に、?演算子を使うと、 関数の戻り値の型を参照して自動的に型の変換を行う ことができます(これはRustの仕様です)。
下記の例では、fetch_article関数はResult<String, FetchError>を返すと明記しているので、関数内でエラーが起きる可能性のある処理を呼び出す時に?演算子を使うと、自動でそのエラーをFetchErrorに変換しようとします。FetchErrorではあらかじめ#[from]属性でreqwest::Errorを指定していたため、明示的にFetchErrorへの変換を書かずとも?演算子で変換後のエラーを返すことができます。

pub async fn fetch_article(url: &str) -> Result<String, FetchError> {
    debug!(url = %url, "Fetching article content");

    let content = reqwest::get(url)
        .await
        .map_err(|e| {
            error!(url = %url, error = %e, "Failed to fetch article URL");
            e
        })?
        .text()
        .await
        .map_err(|e| {
            error!(url = %url, error = %e, "Failed to read article content");
            e
        })?;

    debug!(
        url = %url,
        size_bytes = content.len(),
        "Article content fetched successfully"
    );

    Ok(content)
}

明示的なエラーの変換

もちろん、明示的に独自のエラー型を返すこともできます。下記は、SDKのエラーを継承しつつ、SdkErrorという独自のエラー型も定義しています(Stringのみを内包していますが、より複雑な構造を内包するのも可能です)。

#[derive(Debug, thiserror::Error)]
pub enum S3RepositoryError {
    #[error("S3 error: {0}")]
    S3Error(#[from] aws_sdk_s3::Error),
    #[error("ByteStream error: {0}")]
    ByteStreamError(#[from] aws_smithy_types::byte_stream::error::Error),
    #[error("S3 SDK error: {0}")]
    SdkError(String),
}

実際にSdkErrorを返すしたいときには以下のように記述します。

impl S3Repository {
    pub async fn new(bucket_name: String) -> Self {
        // ...
    }

    pub async fn put_object(&self, key: &str, body: Vec<u8>) -> Result<(), S3RepositoryError> {
        // ...

        self.client
            .put_object()
            .bucket(&self.bucket_name)
            .key(key)
            .body(body.into())
            .send()
            .await
            .map_err(|e| {
                error!(
                    bucket = %self.bucket_name,
                    key = %key,
                    error = %e,
                    "Failed to upload object to S3"
                );
                S3RepositoryError::SdkError(e.to_string())
            })?;

        info!(
            bucket = %self.bucket_name,
            key = %key,
            size_bytes = body_size,
            "Object uploaded to S3 successfully"
        );
        Ok(())
    }

    pub async fn get_object(&self, key: &str) -> Result<Vec<u8>, S3RepositoryError> {
        // ...
    }
}

.map_errで発生したエラーを独自のS3RepositoryError::SdkErrorに明示的に変換しています。そのチェーンに?演算子をつけているので、最終的にS3RepositoryErrorを返すことができます。

※SDKが返すであろうaws_sdk_s3::ErrorS3RepositoryErrorに含めているのでそちらを使ってもいいのですが、なぜかこういう実装にしてみました。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?