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 */))
}