Error Logging

use log::{error, warn, info};
use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("Configuration error: {0}")]
    Config(String),
    
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("Database error: {0}")]
    Database(String),
}

fn process_request(request: Request) -> Result<Response, AppError> {
    info!("Processing request: {:?}", request);
    
    let config = load_config()
        .map_err(|e| {
            error!("Failed to load configuration: {}", e);
            AppError::Config(format!("Failed to load configuration: {}", e))
        })?;
    
    let data = read_data(&request.path)
        .map_err(|e| {
            warn!("Failed to read data: {}", e);
            e
        })?;
    
    let result = process_data(data, &config)
        .map_err(|e| {
            error!("Failed to process data: {}", e);
            AppError::Database(format!("Failed to process data: {}", e))
        })?;
    
    info!("Request processed successfully");
    
    Ok(Response::new(result))
}

Error Recovery Strategies

use std::time::Duration;
use std::thread;

// Retry pattern
fn with_retry<F, T, E>(mut operation: F, max_attempts: usize) -> Result<T, E>
where
    F: FnMut() -> Result<T, E>,
    E: std::fmt::Debug,
{
    let mut attempts = 0;
    let mut last_error = None;
    
    while attempts < max_attempts {
        match operation() {
            Ok(value) => return Ok(value),
            Err(e) => {
                log::warn!("Operation failed (attempt {}): {:?}", attempts + 1, e);
                last_error = Some(e);
                attempts += 1;
                
                if attempts < max_attempts {
                    let backoff = Duration::from_millis(100 * 2u64.pow(attempts as u32));
                    thread::sleep(backoff);
                }
            }
        }
    }
    
    Err(last_error.unwrap())
}

// Fallback pattern
fn with_fallback<F, G, T, E>(primary: F, fallback: G) -> Result<T, E>
where
    F: FnOnce() -> Result<T, E>,
    G: FnOnce() -> Result<T, E>,
{
    match primary() {
        Ok(value) => Ok(value),
        Err(e) => {
            log::warn!("Primary operation failed, trying fallback: {:?}", e);
            fallback()
        }
    }
}

Error Handling in Async Code

Async code introduces additional considerations for error handling:

Async Error Handling Basics

use tokio::fs;
use anyhow::{Context, Result};

async fn read_file(path: &str) -> Result<String> {
    fs::read_to_string(path)
        .await
        .with_context(|| format!("Failed to read file: {}", path))
}

async fn process_file(path: &str) -> Result<()> {
    let content = read_file(path).await?;
    
    // Process content...
    
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    let result = process_file("data.txt").await;
    
    if let Err(e) = &result {
        eprintln!("Error: {}", e);
        
        // Print the error chain
        let mut source = e.source();
        while let Some(cause) = source {
            eprintln!("Caused by: {}", cause);
            source = cause.source();
        }
    }
    
    result
}

Error Handling with Stream Processing

use futures::stream::{self, StreamExt, TryStreamExt};
use anyhow::{Context, Result};

async fn process_files(paths: Vec<String>) -> Result<Vec<String>> {
    // Process files concurrently with error handling
    let results = stream::iter(paths)
        .map(|path| async move {
            let content = tokio::fs::read_to_string(&path)
                .await
                .with_context(|| format!("Failed to read file: {}", path))?;
            
            let processed = process_content(&content)
                .with_context(|| format!("Failed to process file: {}", path))?;
            
            Ok::<_, anyhow::Error>(processed)
        })
        .buffer_unordered(10) // Process up to 10 files concurrently
        .collect::<Vec<_>>()
        .await;
    
    // Collect successful results and report errors
    let mut processed_contents = Vec::new();
    let mut error_count = 0;
    
    for result in results {
        match result {
            Ok(content) => processed_contents.push(content),
            Err(e) => {
                error_count += 1;
                eprintln!("Error: {}", e);
            }
        }
    }
    
    if error_count > 0 {
        eprintln!("{} files failed to process", error_count);
    }
    
    Ok(processed_contents)
}

Domain-Specific Error Handling

Different domains may require specialized error handling approaches:

Web API Error Handling

use actix_web::{web, App, HttpResponse, HttpServer, ResponseError};
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug)]
enum ApiError {
    #[error("Not found: {0}")]
    NotFound(String),
    
    #[error("Validation error: {0}")]
    ValidationError(String),
    
    #[error("Unauthorized: {0}")]
    Unauthorized(String),
    
    #[error("Internal server error")]
    InternalError(#[from] anyhow::Error),
}

#[derive(Serialize)]
struct ErrorResponse {
    error: String,
    code: u16,
}

impl ResponseError for ApiError {
    fn error_response(&self) -> HttpResponse {
        let (status_code, error_code) = match self {
            ApiError::NotFound(_) => (actix_web::http::StatusCode::NOT_FOUND, 404),
            ApiError::ValidationError(_) => (actix_web::http::StatusCode::BAD_REQUEST, 400),
            ApiError::Unauthorized(_) => (actix_web::http::StatusCode::UNAUTHORIZED, 401),
            ApiError::InternalError(_) => (actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, 500),
        };
        
        HttpResponse::build(status_code).json(ErrorResponse {
            error: self.to_string(),
            code: error_code,
        })
    }
}

#[derive(Deserialize)]
struct CreateUserRequest {
    username: String,
    email: String,
}

async fn create_user(req: web::Json<CreateUserRequest>) -> Result<HttpResponse, ApiError> {
    // Validate request
    if req.username.is_empty() {
        return Err(ApiError::ValidationError("Username cannot be empty".to_string()));
    }
    
    if !req.email.contains('@') {
        return Err(ApiError::ValidationError("Invalid email format".to_string()));
    }
    
    // Create user...
    
    Ok(HttpResponse::Created().json(/* user */))
}