Thiserror: Rustにおける効果的なエラー quản lý
Grace Collins
Solutions Engineer · Leapcell

Error Handling
プログラミングにおいて、エラーハンドリングは重要な部分です。Rustでは、エラーハンドリングによくResult
とOption
の型を使用します。ただし、カスタムエラー型を作成する必要がある場合もあります。ここで、コードを大幅に簡素化するthiserror
クレートが登場します。記事の最後に、thiserror
を使用する場合と使用しない場合の比較があります。
Overview of the thiserror
Crate
thiserror
クレートの主な目標は、Rustでのカスタムエラーの作成と処理を簡素化することです。プロジェクトでthiserror
を使用するには、まずCargo.toml
に追加します。
[dependencies] thiserror = "1.0"
Creating Custom Errors
thiserror
クレートは、Rustのderive
マクロとカスタム属性を組み合わせることで、開発者がカスタムエラー型を迅速に作成できるようにします。
Example:
use thiserror::Error; // Definition of a custom error type #[derive(Error, Debug)] pub enum MyError { // Description for DataNotFound error #[error("data not found")] DataNotFound, // Description for InvalidInput error #[error("invalid input")] InvalidInput, } // Example function showing how to use custom errors fn search_data(query: &str) -> Result<(), MyError> { if query.is_empty() { // Return InvalidInput error when the query is empty return Err(MyError::InvalidInput); } // The actual data query logic is omitted here // ... // Return DataNotFound error when data is not found Err(MyError::DataNotFound) }
ここで、MyError
は定義したカスタムエラーenumです。各変数は#[error("...")]
属性でアノテーションが付けられており、エラーがトリガーされたときに表示されるメッセージを提供します。
Nested Errors
エラーチェーンにより、基盤となるライブラリまたは関数から伝播されるエラーをキャプチャして対応できます。thiserror
は、エラーが別のエラーによって発生したことを指定する方法を提供します。
Example:
use std::io; use thiserror::Error; // Definition of a custom error type #[derive(Error, Debug)] pub enum MyError { // Description for IoError, which contains a nested io::Error #[error("I/O error occurred")] IoError(#[from] io::Error), } // Example function showing how to use nested errors fn read_file(file_path: &str) -> Result<String, MyError> { // If fs::read_to_string returns an error, we use MyError::from to convert it into MyError::IoError std::fs::read_to_string(file_path).map_err(MyError::from) }
#[from]
属性は、io::Error
がMyError::IoError
に自動的に変換できることを示します。
Dynamic Error Messages
動的エラーメッセージを使用すると、ランタイムデータに基づいてエラーメッセージを生成できます。
Example:
use thiserror::Error; // Definition of a custom error type #[derive(Error, Debug)] pub enum MyError { // Description for FailedWithCode, where {0} will be dynamically replaced with the actual code value #[error("failed with code: {0}")] FailedWithCode(i32), } // Example function showing how to use dynamic error messages fn process_data(data: &str) -> Result<(), MyError> { let error_code = 404; // Some computed error code // Use the dynamic error_code to create a FailedWithCode error Err(MyError::FailedWithCode(error_code)) }
Cross-Library and Cross-Module Error Handling
thiserror
は、他のエラー型からの自動変換もサポートしています。これは、モジュールまたはライブラリ全体でのエラー処理に特に役立ちます。
Example:
use thiserror::Error; // Simulated error type imported from another library #[derive(Debug, Clone)] pub struct OtherLibError; // Definition of a custom error type #[derive(Error, Debug)] pub enum MyError { // Description for OtherError, which directly inherits from its inner error type #[error(transparent)] OtherError(#[from] OtherLibError), } // Example function showing how to convert from another error type fn interface_with_other_lib() -> Result<(), MyError> { // Call a function from another library... // If that function returns an error, we use MyError::from to convert it into MyError::OtherError Err(MyError::from(OtherLibError)) }
#[error(transparent)]
属性は、このエラーが別のエラーのコンテナとして機能するだけであり、そのエラーメッセージはその「ソース」エラーから直接継承されることを意味します。
Comparison with Other Error Handling Crates
thiserror
は非常に便利ですが、利用できるエラー処理クレートはこれだけではありません。たとえば、anyhow
は、迅速なプロトタイピングとアプリケーション開発に使用されるもう1つの一般的なクレートです。ただし、thiserror
は、より柔軟なエラー定義とパターンマッチング機能を提供します。
Practical Case
ファイルの読み取りと解析を含む操作を検討してください。潜在的なI/Oエラーと解析エラーを処理する必要があります。
Example:
use std::fs; use thiserror::Error; // Simulated parse error type imported from another part #[derive(Debug, Clone)] pub struct ParseDataError; // Definition of a custom error type #[derive(Error, Debug)] pub enum MyError { // Description for IoError, containing a nested io::Error #[error("I/O error occurred")] IoError(#[from] io::Error), // Description for ParseError, containing a nested ParseDataError #[error("failed to parse data")] ParseError(#[from] ParseDataError), } // Read a file and attempt to parse its contents fn read_and_parse(filename: &str) -> Result<String, MyError> { // Read file contents, may throw an I/O error let content = fs::read_to_string(filename)?; // Attempt to parse contents, may throw a parse error parse_data(&content).map_err(MyError::from) } // Simulated data parsing function, which always returns an error here fn parse_data(content: &str) -> Result<String, ParseDataError> { Err(ParseDataError) } // Main function demonstrating how to use the above error handling logic fn main() { match read_and_parse("data.txt") { Ok(data) => println!("Data: {}", data), Err(e) => eprintln!("Error: {}", e), } }
Comparison: Using thiserror
vs Not Using thiserror
複数のソースから発生する可能性のある複数のエラーを含む、より複雑な例を考えてみましょう。
リモートAPIからデータを取得し、そのデータをデータベースに保存する必要があるアプリケーションを作成しているとします。各ステップは失敗し、異なるタイプのエラーを返す可能性があります。
Code Without Using thiserror
:
use std::fmt; #[derive(Debug)] enum DataFetchError { HttpError(u16), Timeout, InvalidPayload, } impl fmt::Display for DataFetchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::HttpError(code) => write!(f, "HTTP error with code: {}", code), Self::Timeout => write!(f, "Data fetching timed out"), Self::InvalidPayload => write!(f, "Invalid payload received"), } } } impl std::error::Error for DataFetchError {} #[derive(Debug)] enum DatabaseError { ConnectionFailed, WriteFailed(String), } impl fmt::Display for DatabaseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ConnectionFailed => write!(f, "Failed to connect to database"), Self::WriteFailed(reason) => write!(f, "Failed to write to database: {}", reason), } } } impl std::error::Error for DatabaseError {}
Code Using thiserror
:
use thiserror::Error; #[derive(Debug, Error)] enum DataFetchError { #[error("HTTP error with code: {0}")] HttpError(u16), #[error("Data fetching timed out")] Timeout, #[error("Invalid payload received")] InvalidPayload, } #[derive(Debug, Error)] enum DatabaseError { #[error("Failed to connect to database")] ConnectionFailed, #[error("Failed to write to database: {0}")] WriteFailed(String), }
Analysis
- Reduced Code: For each error type, we no longer need separate
Display
andError
trait implementations. This greatly reduces boilerplate code and improves code readability. - Error Messages Co-located with Definitions: Using
thiserror
, we can write the error message directly next to the error definition. This makes the code more organized and easier to locate and modify. - Increased Maintainability: If we need to add or remove error types, we only need to modify the enum definition and update the error messages, without needing to change other parts of the code.
Thus, as our error types and scenarios become more complex, the advantages of using thiserror
become more apparent.
We are Leapcell, your top choice for hosting Rust projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ