In this lesson, you will learn how to design custom error enums that carry additional metadata, allowing you to provide specific context when an operation fails.
When you propagate an error using the ? operator, a simple enum variant like Error::NotFound might tell you what happened, but it doesn't tell you where or why. By leveraging Rust’s ability to store data within enum variants, you can bundle helpful information directly into the error.
The Power of Variant Payloads
Instead of defining an error as a plain unit variant, you can use tuple or struct variants to hold contextual fields like file paths, configuration keys, or IDs.
Consider this example of a file-processing utility:
enum FileError {
NotFound(String), // Stores the missing filename
PermissionDenied(String), // Stores the path that failed
ParseError(usize, String), // Stores the line number and message
}
By embedding the String or usize directly into the variant, you move from a generic error to an actionable one. When you return this error, your calling function doesn't just know that a file was missing—it knows exactly which file was expected.
Propagating with Context
When you catch a low-level error, you can map it into your domain-specific enum to add the context:
fn read_config(path: &str) -> Result<String, FileError> {
std::fs::read_to_string(path)
.map_err(|_| FileError::NotFound(path.to_string()))
}
Here, map_err bridges the gap between the standard library’s generic std::io::Error and your enriched FileError. By capturing the path variable and moving it into the enum, you preserve the context that would otherwise be lost when the error bubbles up the call stack. This practice transforms vague "something went wrong" logs into precise, debuggable diagnostic messages that identify the specific point of failure in your application logic.
Key Takeaway: Embedding data within your error variants allows you to bundle actionable metadata with your errors, turning generic failure points into informative reports.