I sometimes wrap errors (to provide an abstraction from lower-level library errors).
There have been some discussions on what's the correct way to implement std::fmt::Display
for std::error::Error
s which have a source
: Do you include a description of the underlying error, or should that be done by the error reporting mechanism?
This is what I would do now:
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub struct InnerError {
description: String,
}
impl fmt::Display for InnerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description)
}
}
impl Error for InnerError{}
#[derive(Debug)]
enum OuterErrorVariant {
A(InnerError),
B(InnerError),
}
#[derive(Debug)]
pub struct OuterError {
variant: OuterErrorVariant,
}
impl Error for OuterError{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.variant {
OuterErrorVariant::A(inner) => Some(inner),
OuterErrorVariant::B(inner) => Some(inner),
}
}
}
impl fmt::Display for OuterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.variant {
OuterErrorVariant::A(_) => write!(f, "error A"),
OuterErrorVariant::B(_) => write!(f, "error B"),
}
}
}
fn run() -> Result<(), Box<dyn Error>> {
let result: Result<(), OuterError> = Err(OuterError {
variant: OuterErrorVariant::A(
InnerError { description: "internal problem".to_string() },
),
});
result?;
Ok(())
}
fn main() {
match run() {
Ok(()) => (),
Err(err) => {
println!("Error: {err}");
let mut outer = &*err;
while let Some(source) = outer.source() {
println!("Cause: {source}");
outer = source;
}
}
}
}
Output:
Error: error A
Cause: internal problem
Is that the best way to go currently?
Further links on the issue:
- PR #210 on API guidelines (currently open)
- Blog post "What the Error Handling Project Group is Working Towards" (over one year old)