この記事はRust+SvelteKit+CDKでRSS要約アプリを作ってみる Advent Calendar 2025の14日目の記事になります。
また、筆者が属している株式会社野村総合研究所のアドベントカレンダーもあるので、ぜひ購読ください。
はじめに
RustはTypeScriptと違って、言語レベルでResultやOptionを提供しており、エラーハンドリングを堅牢に実装することができます。Resultは、
- 成功時の値
- 失敗時の
Error
の二つを内包しています。TypeScriptでは(デフォルトでは)何かエラーが発生した場合は例外を投げることで表現しますが、RustではResultにErrorを詰めることで表現します。この機構は非常に便利で安全なのですが、処理によっては複数のエラーパターンをまとめた独自の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トレイトを実装します。
各エラーの構造は自由に定義できますが、他の独自のエラーから派生させることもできます。今回はreqwestとfeed-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::ErrorもS3RepositoryErrorに含めているのでそちらを使ってもいいのですが、なぜかこういう実装にしてみました。