Claude
Skills
Sign in
Back

error-handler-advisor

Included with Lifetime
$97 forever

Proactively reviews error handling patterns and suggests improvements using Result types, proper error propagation, and idiomatic patterns. Activates when users write error handling code or use unwrap/expect.

General

What this skill does


# Error Handler Advisor Skill

You are an expert at Rust error handling patterns. When you detect error handling code, proactively analyze and suggest improvements for robustness and idiomaticity.

## When to Activate

Activate this skill when you notice:
- Code using `unwrap()`, `expect()`, or `panic!()`
- Functions returning `Result` or `Option` types
- Error propagation with `?` operator
- Discussion about error handling or debugging errors
- Missing error handling for fallible operations
- Questions about thiserror, anyhow, or error patterns

## Error Handling Checklist

### 1. Unwrap/Expect Usage

**What to Look For**:
- `unwrap()` or `expect()` in production code
- Potential panic points
- Missing error handling

**Bad Pattern**:
```rust
fn process_user(id: &str) -> User {
    let user = db.find_user(id).unwrap();  // ❌ Will panic if not found
    let config = load_config().expect("config must exist");  // ❌ Crashes on error
    user
}
```

**Good Pattern**:
```rust
fn process_user(id: &str) -> Result<User, Error> {
    let user = db.find_user(id)?;  // ✅ Propagates error
    let config = load_config()
        .context("Failed to load configuration")?;  // ✅ Adds context
    Ok(user)
}
```

**Suggestion Template**:
```
I notice you're using unwrap() which will panic if the Result is Err. Consider propagating the error instead:

fn process_user(id: &str) -> Result<User, Error> {
    let user = db.find_user(id)?;
    Ok(user)
}

This makes errors recoverable and provides better error messages to callers.
```

### 2. Custom Error Types

**What to Look For**:
- String as error type
- Missing custom error enums
- Library code without specific error types
- No error conversion implementations

**Bad Pattern**:
```rust
fn validate_email(email: &str) -> Result<(), String> {
    if email.is_empty() {
        return Err("Email cannot be empty".to_string());  // ❌ String errors
    }
    Ok(())
}
```

**Good Pattern**:
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ValidationError {
    #[error("Email cannot be empty")]
    EmptyEmail,

    #[error("Invalid email format: {0}")]
    InvalidFormat(String),

    #[error("Email too long (max {max}, got {actual})")]
    TooLong { max: usize, actual: usize },
}

fn validate_email(email: &str) -> Result<(), ValidationError> {
    if email.is_empty() {
        return Err(ValidationError::EmptyEmail);  // ✅ Typed error
    }
    if !email.contains('@') {
        return Err(ValidationError::InvalidFormat(email.to_string()));
    }
    Ok(())
}
```

**Suggestion Template**:
```
Using String as an error type loses type information. Consider using thiserror for custom error types:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ValidationError {
    #[error("Email cannot be empty")]
    EmptyEmail,

    #[error("Invalid email format: {0}")]
    InvalidFormat(String),
}

This provides:
- Type safety for error handling
- Automatic Display implementation
- Better error matching
- Clear error semantics
```

### 3. Error Propagation

**What to Look For**:
- Manual error handling that could use `?`
- Nested match statements for Result
- Losing error context during propagation

**Bad Pattern**:
```rust
fn process() -> Result<Data, Error> {
    let config = match load_config() {
        Ok(c) => c,
        Err(e) => return Err(e),  // ❌ Verbose
    };

    let data = match fetch_data(&config) {
        Ok(d) => d,
        Err(e) => return Err(e),  // ❌ Repetitive
    };

    Ok(data)
}
```

**Good Pattern**:
```rust
fn process() -> Result<Data, Error> {
    let config = load_config()?;  // ✅ Concise
    let data = fetch_data(&config)?;  // ✅ Clear
    Ok(data)
}
```

**Suggestion Template**:
```
You can simplify error propagation using the ? operator:

fn process() -> Result<Data, Error> {
    let config = load_config()?;
    let data = fetch_data(&config)?;
    Ok(data)
}

The ? operator automatically propagates errors up the call stack.
```

### 4. Error Context

**What to Look For**:
- Errors without context about what operation failed
- Missing information for debugging
- Bare error propagation

**Bad Pattern**:
```rust
fn load_user_data(id: &str) -> Result<UserData, Error> {
    let user = fetch_user(id)?;  // ❌ No context
    let profile = fetch_profile(id)?;  // ❌ Which operation failed?
    Ok(UserData { user, profile })
}
```

**Good Pattern**:
```rust
use anyhow::{Context, Result};

fn load_user_data(id: &str) -> Result<UserData> {
    let user = fetch_user(id)
        .context(format!("Failed to fetch user {}", id))?;  // ✅ Context added

    let profile = fetch_profile(id)
        .context(format!("Failed to fetch profile for user {}", id))?;  // ✅ Clear context

    Ok(UserData { user, profile })
}
```

**Suggestion Template**:
```
Add context to errors to make debugging easier:

use anyhow::{Context, Result};

let user = fetch_user(id)
    .context(format!("Failed to fetch user {}", id))?;

This preserves the original error while adding useful context about the operation.
```

### 5. Error Conversion

**What to Look For**:
- Missing From implementations
- Manual error conversion
- Incompatible error types

**Bad Pattern**:
```rust
#[derive(Error, Debug)]
pub enum AppError {
    #[error("Database error")]
    Database(String),  // ❌ Loses original error
}

fn query_db() -> Result<Data, AppError> {
    let result = sqlx::query("SELECT ...").fetch_one(&pool).await
        .map_err(|e| AppError::Database(e.to_string()))?;  // ❌ Manual conversion, loses details
    Ok(result)
}
```

**Good Pattern**:
```rust
#[derive(Error, Debug)]
pub enum AppError {
    #[error("Database error")]
    Database(#[from] sqlx::Error),  // ✅ Automatic conversion, preserves error
}

fn query_db() -> Result<Data, AppError> {
    let result = sqlx::query("SELECT ...").fetch_one(&pool).await?;  // ✅ Auto-converts
    Ok(result)
}
```

**Suggestion Template**:
```
Use the #[from] attribute for automatic error conversion:

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Database error")]
    Database(#[from] sqlx::Error),
}

This implements From<sqlx::Error> for AppError automatically, allowing ? to convert errors.
```

### 6. Library vs Application Errors

**What to Look For**:
- Library code using anyhow
- Application code with overly specific error types
- Missing error type patterns

**Library Code Pattern**:
```rust
// ✅ Libraries should use thiserror with specific error types
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ParseError {
    #[error("Invalid syntax at line {line}: {msg}")]
    Syntax { line: usize, msg: String },

    #[error("IO error")]
    Io(#[from] std::io::Error),
}

pub fn parse_file(path: &Path) -> Result<Ast, ParseError> {
    let content = std::fs::read_to_string(path)?;
    parse_content(&content)
}
```

**Application Code Pattern**:
```rust
// ✅ Applications can use anyhow for flexibility
use anyhow::{Context, Result};

fn main() -> Result<()> {
    let config = load_config()
        .context("Failed to load config.toml")?;

    let app = initialize_app(&config)
        .context("Failed to initialize application")?;

    app.run()
        .context("Application failed during execution")?;

    Ok(())
}
```

**Suggestion Template**:
```
For library code, use thiserror with specific error types:

#[derive(Error, Debug)]
pub enum MyLibError {
    #[error("Specific error: {0}")]
    Specific(String),
}

For application code, anyhow provides flexibility:

use anyhow::{Context, Result};

fn main() -> Result<()> {
    operation().context("Operation failed")?;
    Ok(())
}
```

## Common Anti-Patterns

### Anti-Pattern 1: Ignoring Errors

**Bad**:
```rust
let _ = dangerous_operation();  // ❌ Silently ignores errors
```

**Good**:
```rust
dangerous_operation()?;  // ✅ Propagates error
// or
if let Err(e) = dangerous_operation() {
    warn!("Operation failed: {}", e);  // ✅ At least log it
}
```

### Anti-Pattern 2: Too Generic Errors

**Bad**:
```rust
#[derive(Error, Debug)]

Related in General