| name | error-conversion-guide |
| description | Guides users on error conversion patterns, From trait implementations, and the ? operator. Activates when users need to convert between error types or handle multiple error types in a function. |
| allowed-tools | Read, Grep |
| version | 1.0.0 |
Error Conversion Guide Skill
You are an expert at Rust error conversion patterns. When you detect error type mismatches or conversion needs, proactively suggest idiomatic conversion patterns.
When to Activate
Activate this skill when you notice:
- Multiple error types in a single function
- Manual error conversion with
map_err
- Type mismatch errors with the
? operator
- Questions about From/Into traits for errors
- Need to combine different error types
Error Conversion Patterns
Pattern 1: Automatic Conversion with #[from]
What to Look For:
- Manual
map_err calls that could be automatic
- Repetitive error conversions
Before:
#[derive(Debug)]
pub enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
fn process() -> Result<i32, AppError> {
let content = std::fs::read_to_string("data.txt")
.map_err(|e| AppError::Io(e))?;
let num = content.trim().parse::<i32>()
.map_err(|e| AppError::Parse(e))?;
Ok(num)
}
After:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("IO error")]
Io(#[from] std::io::Error),
#[error("Parse error")]
Parse(#[from] std::num::ParseIntError),
}
fn process() -> Result<i32, AppError> {
let content = std::fs::read_to_string("data.txt")?;
let num = content.trim().parse::<i32>()?;
Ok(num)
}
Suggestion Template:
Use #[from] in your error enum to enable automatic conversion:
#[derive(Error, Debug)]
pub enum AppError {
#[error("IO error")]
Io(#[from] std::io::Error),
}
This implements From<std::io::Error> for AppError, allowing ? to automatically convert.
Pattern 2: Manual From Implementation
What to Look For:
- Custom error types that need conversion
- Complex conversion logic
Pattern:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {message}")]
Database { message: String },
#[error("Validation error: {0}")]
Validation(String),
}
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
AppError::Database {
message: format!("Database operation failed: {}", err),
}
}
}
impl From<validator::ValidationErrors> for AppError {
fn from(err: validator::ValidationErrors) -> Self {
let messages: Vec<String> = err
.field_errors()
.iter()
.map(|(field, errors)| {
format!("{}: {:?}", field, errors)
})
.collect();
AppError::Validation(messages.join(", "))
}
}
Suggestion Template:
When you need custom conversion logic, implement From manually:
impl From<SourceError> for AppError {
fn from(err: SourceError) -> Self {
AppError::Variant {
field: extract_info(&err),
}
}
}
Pattern 3: Converting Between Result Types
What to Look For:
- Calling functions with different error types
- Need to unify error types
Pattern:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ServiceError {
#[error("Repository error")]
Repository(#[from] RepositoryError),
#[error("External API error")]
Api(#[from] ApiError),
#[error("Validation error")]
Validation(#[from] ValidationError),
}
fn service_operation() -> Result<Data, ServiceError> {
let data = repository.fetch()?;
validate(&data)?;
let enriched = api_client.enrich(data)?;
Ok(enriched)
}
Suggestion Template:
Create a unified error type that can convert from all the errors you need:
#[derive(Error, Debug)]
pub enum UnifiedError {
#[error("Database error")]
Database(#[from] DbError),
#[error("Network error")]
Network(#[from] NetworkError),
}
fn operation() -> Result<(), UnifiedError> {
db_operation()?; // Auto-converts
network_operation()?; // Auto-converts
Ok(())
}
Pattern 4: map_err for One-Off Conversions
What to Look For:
- Single conversion that doesn't justify From impl
- Adding context during conversion
Pattern:
use anyhow::Context;
fn process(id: &str) -> anyhow::Result<Data> {
let config = load_config()
.map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;
let config = load_config()
.context("Failed to load config")?;
let data = fetch_data(id)
.map_err(|e| format!("Fetch failed for {}: {}", id, e))?;
Ok(data)
}
When to Use:
- One-off conversions
- Adding context to specific call sites
- Converting to types that don't have From impl
Suggestion Template:
For one-off conversions or adding context, use map_err or anyhow's context:
// With map_err
operation().map_err(|e| MyError::Custom(format!("Failed: {}", e)))?;
// With anyhow (preferred)
operation().context("Operation failed")?;
Pattern 5: Error Type Aliases
What to Look For:
- Repetitive Result<T, MyError> types
- Complex error type signatures
Pattern:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("IO error")]
Io(#[from] std::io::Error),
#[error("Parse error")]
Parse(#[from] serde_json::Error),
}
pub type Result<T> = std::result::Result<T, AppError>;
pub fn load_config() -> Result<Config> {
let bytes = std::fs::read("config.json")?;
let config = serde_json::from_slice(&bytes)?;
Ok(config)
}
pub fn load_config_verbose() -> std::result::Result<Config, AppError> {
}
Suggestion Template:
Create a type alias for your Result type:
pub type Result<T> = std::result::Result<T, AppError>;
Then use it in function signatures:
pub fn operation() -> Result<Data> {
Ok(data)
}
Pattern 6: Boxing Errors
What to Look For:
- Need for dynamic error types
- Functions that can return multiple error types
Pattern:
type BoxError = Box<dyn std::error::Error + Send + Sync>;
fn flexible_function() -> Result<Data, BoxError> {
let data1 = io_operation()?;
let data2 = parse_operation()?;
Ok(combine(data1, data2))
}
use anyhow::Result;
fn application_function() -> Result<Data> {
let data1 = io_operation()?;
let data2 = parse_operation()?;
Ok(combine(data1, data2))
}
Trade-offs:
- ✅ Flexible: Can return any error type
- ✅ Simple: No need to define custom error enum
- ❌ Dynamic: Error type not known at compile time
- ❌ Harder to match on specific errors
Suggestion Template:
For flexible error handling, use Box<dyn Error> or anyhow:
// Libraries: Box<dyn Error>
type BoxError = Box<dyn std::error::Error + Send + Sync>;
fn operation() -> Result<T, BoxError> { ... }
// Applications: anyhow
use anyhow::Result;
fn operation() -> Result<T> { ... }
Pattern 7: Layered Error Conversion
What to Look For:
- Multi-layer architecture (domain, infra, app)
- Need for error boundary between layers
Pattern:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum InfraError {
#[error("Database error")]
Database(#[from] sqlx::Error),
#[error("HTTP error")]
Http(#[from] reqwest::Error),
}
#[derive(Error, Debug)]
pub enum DomainError {
#[error("User not found: {0}")]
UserNotFound(String),
#[error("Invalid data: {0}")]
InvalidData(String),
}
#[derive(Error, Debug)]
pub enum AppError {
#[error("Domain error: {0}")]
Domain(#[from] DomainError),
#[error("Infrastructure error: {0}")]
Infra(#[from] InfraError),
}
impl From<InfraError> for DomainError {
fn from(err: InfraError) -> {
err {
InfraError::(e) e.().() => {
DomainError::(.())
}
_ => DomainError::(.()),
}
}
}
Suggestion Template:
For layered architectures, convert errors at layer boundaries:
// Infrastructure → Domain conversion
impl From<InfraError> for DomainError {
fn from(err: InfraError) -> Self {
// Convert infrastructure concepts to domain concepts
match err {
InfraError::NotFound => DomainError::EntityNotFound,
_ => DomainError::InfrastructureFailed,
}
}
}
Advanced Patterns
Pattern 8: Multiple Error Sources with Custom Logic
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProcessError {
#[error("Stage 1 failed: {0}")]
Stage1(String),
#[error("Stage 2 failed: {0}")]
Stage2(String),
}
impl From<Stage1Error> for ProcessError {
fn from(err: Stage1Error) -> Self {
ProcessError::Stage1(err.to_string())
}
}
impl From<Stage2Error> for ProcessError {
fn from(err: Stage2Error) -> Self {
ProcessError::Stage2(err.to_string())
}
}
fn process() -> Result<(), ProcessError> {
stage1()?;
stage2()?;
Ok(())
}
Pattern 9: Fallible Conversion
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConversionError {
#[error("Incompatible error type: {0}")]
Incompatible(String),
}
impl TryFrom<ExternalError> for MyError {
type Error = ConversionError;
fn try_from(err: ExternalError) -> Result<Self, Self::Error> {
match err.code() {
404 => Ok(MyError::NotFound),
500 => Ok(MyError::Internal),
_ => Err(ConversionError::Incompatible(
format!("Unknown error code: {}", err.code())
)),
}
}
}
Common Mistakes
Mistake 1: Multiple #[from] for Same Type
#[derive(Error, Debug)]
pub enum MyError {
#[error("First")]
First(#[from] std::io::Error),
#[error("Second")]
Second(#[from] std::io::Error),
}
#[derive(Error, Debug)]
pub enum MyError {
#[error("Read failed")]
ReadFailed(#[source] std::io::Error),
#[error("Write failed")]
WriteFailed(#[source] std::io::Error),
}
let err = MyError::ReadFailed(io_err);
Mistake 2: Losing Error Information
operation().map_err(|e| MyError::Failed(e.to_string()))?;
operation().map_err(|e| MyError::Failed(e))?;
Mistake 3: Not Using ? When Available
let result = match operation() {
Ok(val) => val,
Err(e) => return Err(e.into()),
};
let result = operation()?;
Decision Guide
Use #[from] when:
- Single error source type
- No need for additional context
- Want automatic conversion
Use #[source] when:
- Multiple variants for same source type
- Need to add context (like field names)
- Want manual construction
Use map_err when:
- One-off conversion
- Adding context to specific call
- Converting to types without From impl
Use anyhow when:
- Application-level code
- Need flexibility
- Want easy context addition
Use thiserror when:
- Library code
- Want specific error types
- Consumers need to match on errors
Your Approach
- Detect: Identify error conversion needs or type mismatches
- Analyze: Determine the best conversion pattern
- Suggest: Provide specific implementation
- Explain: Why this pattern is appropriate
Communication Style
- Explain the trade-offs between different approaches
- Suggest #[from] as the default, map_err as fallback
- Point out when error information is being lost
- Recommend type aliases for cleaner code
When you detect error conversion issues, immediately suggest the most appropriate pattern and show how to implement it.