I'll hype my own library, SNAFU [1].<p>It simplifies constructing your own "leaf" errors and streamlines the ability of collecting multiple types of errors while attaching more context to them (e.g. filenames, stack traces, user ids, etc.). It allows you to smoothly switch from "stringly-typed" errors to strongly-typed errors. You can create opaque errors to avoid leaking internal implementation details into your public API.<p>Applied to the code in the post:<p><pre><code> use snafu::prelude::*;
use std::{fs::File, io::prelude::*};
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Unable to open {filename}"))]
Opening {
source: std::io::Error,
filename: String,
},
#[snafu(display("Unable to read {filename}"))]
Reading {
source: std::io::Error,
filename: String,
},
#[snafu(display("Unable to parse {buffer} as a number"))]
Parsing {
source: std::num::ParseIntError,
buffer: String,
},
}
fn read_number_from_file(filename: &str) -> Result<u64, Error> {
let mut file = File::open(filename).context(OpeningSnafu { filename })?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)
.context(ReadingSnafu { filename })?;
let buffer = buffer.trim();
let parsed: u64 = buffer.parse().context(ParsingSnafu { buffer })?;
Ok(parsed)
}
</code></pre>
The key parts are the `derive(Snafu)` on the definition of the error enum and the usages of `.context` and `XxxSnafu` at the error sites.<p>Importantly, this example demonstrates a key feature of SNAFU, here shown as "not all `io::Error`s are the same". Opening the file and reading the file are two separate error conditions and should <i>not</i> be lumped together as one.<p>[1]: <a href="https://docs.rs/snafu/0.7.0-beta.1/snafu/index.html" rel="nofollow">https://docs.rs/snafu/0.7.0-beta.1/snafu/index.html</a>