mirror of
https://github.com/leonardomso/rust-skills.git
synced 2026-09-14 19:33:21 +08:00
0016d5cfb2
Includes rules for: - Ownership and borrowing patterns - Error handling with thiserror/anyhow - Memory management and allocation - API design following Rust guidelines - Async/Tokio patterns - Performance optimization - Naming conventions - Type safety - Testing strategies - Documentation standards - Project structure - Linting configuration - Common anti-patterns to avoid
3.5 KiB
3.5 KiB
err-context-chain
Add context with
.context()or.with_context()
Why It Matters
Raw errors often lack information about what operation failed. Adding context creates an error chain that tells the full story: what you were trying to do, and why it failed.
Bad
// Raw error - no context
fn load_user(id: u64) -> Result<User, Error> {
let path = format!("users/{}.json", id);
let content = std::fs::read_to_string(&path)?;
Ok(serde_json::from_str(&content)?)
}
// Error message: "No such file or directory (os error 2)"
// Which file? What were we doing?
Good
use anyhow::{Context, Result};
fn load_user(id: u64) -> Result<User> {
let path = format!("users/{}.json", id);
let content = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read user file: {}", path))?;
let user: User = serde_json::from_str(&content)
.with_context(|| format!("failed to parse user {} JSON", id))?;
Ok(user)
}
// Error: "failed to parse user 42 JSON"
// Caused by: "expected ':' at line 5 column 12"
context() vs with_context()
// context() - static string (slight allocation)
fs::read_to_string(path)
.context("failed to read config")?;
// with_context() - lazy evaluation (only allocates on error)
fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path))?;
// Use with_context() when:
// - Message includes runtime data (format!)
// - Computing the message is expensive
// - Error path is cold (most of the time)
Building Context Chains
fn process_order(order_id: u64) -> Result<()> {
let order = fetch_order(order_id)
.with_context(|| format!("failed to fetch order {}", order_id))?;
let user = load_user(order.user_id)
.with_context(|| format!("failed to load user for order {}", order_id))?;
let payment = process_payment(&order, &user)
.context("payment processing failed")?;
ship_order(&order, &payment)
.context("shipping failed")?;
Ok(())
}
// Full error chain:
// "shipping failed"
// Caused by: "carrier API returned 503"
// Caused by: "connection refused"
Displaying Error Chains
fn main() {
if let Err(e) = run() {
// Just top-level message
eprintln!("Error: {}", e);
// Full chain with alternate format
eprintln!("Error: {:#}", e);
// Debug format (includes backtrace if enabled)
eprintln!("Error: {:?}", e);
// Iterate through chain
for (i, cause) in e.chain().enumerate() {
eprintln!(" {}: {}", i, cause);
}
}
}
With thiserror
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("failed to load config from {path}")]
ConfigLoad {
path: String,
#[source]
cause: std::io::Error,
},
#[error("failed to connect to database")]
Database {
#[source]
cause: sqlx::Error,
},
}
// Usage
fn load_config(path: &str) -> Result<Config, AppError> {
let content = std::fs::read_to_string(path)
.map_err(|e| AppError::ConfigLoad {
path: path.to_string(),
cause: e,
})?;
// ...
}
See Also
- err-anyhow-app - Use anyhow for applications
- err-source-chain - Use #[source] to chain errors
- err-question-mark - Use ? for propagation