How do I define custom error types in Rust?
Walkthrough
Rust encourages handling errors explicitly through the Result type. While you can use Box<dyn Error> or string errors, defining your own error types leads to clearer APIs and better error handling. The thiserror crate simplifies this process with a derive macro.
Key concepts:
- Create an enum to represent different error cases
- Derive
ErrorandDebugtraits usingthiserror - Use
#[error("...")}attributes to define error messages - Use
#[from]to automatically implementFromfor easy error conversion
This approach gives you type-safe errors that integrate seamlessly with Rust's ? operator while keeping your code clean and maintainable.
Code Example
# Cargo.toml
[dependencies]
thiserror = "1.0"use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("Configuration file not found: {0}")
ConfigMissing(String),
#[error("Failed to read data: {0}")
ReadError(#[from] io::Error),
#[error("Invalid data format at line {line}: {message}")
InvalidFormat { line: usize, message: String },
#[error("Connection failed to {host}:{port}")
ConnectionFailed { host: String, port: u16 },
#[error("Unauthorized access for user '{username}'")
Unauthorized { username: String },
}
// Example function that returns a custom error
fn parse_config(contents: &str) -> Result<Config, DataStoreError> {
if contents.is_empty() {
return Err(DataStoreError::ConfigMissing(
"config file is empty".to_string()
));
}
// Parsing logic here...
Ok(Config { /* ... */ })
}
// Example showing automatic conversion with #[from]
fn read_config_file(path: &str) -> Result<String, DataStoreError> {
// io::Error automatically converts to DataStoreError::ReadError
std::fs::read_to_string(path)?;
Ok(String::new())
}
struct Config {
// fields omitted
}
fn main() -> Result<(), DataStoreError> {
// These errors display nicely with Display trait
let err1 = DataStoreError::InvalidFormat {
line: 42,
message: "unexpected token".to_string(),
};
println!("Error: {}", err1);
// Output: Error: Invalid data format at line 42: unexpected token
let err2 = DataStoreError::ConnectionFailed {
host: "localhost".to_string(),
port: 5432,
};
println!("Error: {}", err2);
// Output: Error: Connection failed to localhost:5432
Ok(())
}Summary
#[derive(Error)]generates thestd::error::Errortrait implementation#[error("...")]defines the display message; use{0}for tuple variants or{field}for struct variants#[from]on a field automatically implementsFrom, enabling?operator conversion- Custom error types provide type safety and clear, user-friendly error messages
- Thiserror is a zero-cost abstractionâit compiles away and adds no runtime overhead
