Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
160: Application-Level Errors with anyhow
By now, you've probably spent a fair amount of time wrestling with Rust's error system. If you've been following the library patterns we discussed previously, you're used to defining exhaustive enums with thiserror to make sure every possible failure is typed and handled. That's great for a library where the caller needs to know exactly what went wrong to decide how to react. But when you're writing the actual application—the CLI tool, the server, the "main" logic—that level of granularity often becomes a burden.
The struggle of the "Everything Enum"
Imagine you're building a small utility that reads a JSON configuration file and then fetches some data from an API. In a "strictly typed" world, your error handling might look like this:
enum AppError {
Io(std::io::Error),
Parse(serde_json::Error),
Http(reqwest::Error),
ConfigMissing(String),
}
impl From<std::io::Error> for AppError { ... }
// ... and so on for every single possible error type
I've seen a lot of developers fall into this trap early on. You spend more time writing From implementations and expanding your enum than you do writing actual business logic. It's exhausting. You're basically building a giant funnel just to eventually print the error to the console and exit the program. If you try to cheat by using Box<dyn std::error::Error>, you lose the ability to easily add context to those errors, leaving your users with a cryptic "No such file or directory" without knowing which file was missing.
Adding context without the boilerplate
This is where anyhow comes in. I think of anyhow::Result as a "catch-all" for application-level errors. It doesn't force you to define a new type for every failure; instead, it provides a type-erased wrapper that can hold any error that implements std::error::Error.
The real magic, however, isn't just the lack of boilerplate—it's the context method. Look at how this changes the way we write the same config-loader logic:
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let content = std::fs::read_to_string("config.json")
.context("failed to read the configuration file")?;
let config = serde_json::from_str(&content)
.context("configuration file was not valid JSON")?;
Ok(config)
}
Notice what happened there. We aren't defining a custom enum. We're just using Result<T> (which is an alias for Result<T, anyhow::Error>). If the file is missing, anyhow doesn't just say "No such file"; it wraps that error and tells the user "failed to read the configuration file: No such file or directory." I love this because it gives me a stack-trace-like experience for logical errors without the performance hit or complexity of a full stack trace.
When to put the anyhow hammer down
You might be wondering if this is "cheating" or if it breaks the type safety Rust is famous for. In a way, it is cheating—you're erasing the specific type of the error. But here's the rule of thumb I use: if you are writing a library that other people will depend on, never use anyhow. Your users need to be able to match on your error types to handle them programmatically.
But if you're writing the "top level" of your application, anyhow is almost always the right choice. At the application level, you usually don't care if an error was a std::io::Error or a serde_json::Error in terms of how you recover—usually, the "recovery" is just logging the error and returning a non-zero exit code. Why fight the compiler to maintain a type distinction that you're never actually going to use?
📋 Practical Task
Build a Robust File-to-API Pipeline
Your task is to refactor a fragile piece of code into a professional application-level pipeline using anyhow.
Create a small program that does the following:
- Reads a filename from a command-line argument.
- Reads the content of that file into a string.
- Attempts to parse that string as an integer.
- Simulates an API call by dividing 100 by that integer (handling the potential division by zero).
Requirements:
- Use
anyhow::Resultas the return type for your functions. - Use
.context()or.with_context()at every stage of the pipeline (e.g., "Failed to read file X", "File content was not a valid number"). - In
main, handle the result and print the error using{:?}to show the full chain of context. - Ensure that if the user provides no command-line argument, the program returns a helpful
anyhowerror instead of panicking.
There are no comments for now.