Do people not care about printable error chains? a.k.a. How to nicely implement Display for an error?

And I never use the source of my error in my own error type, except for returning it from the source() method of course.

This is the correct answer.

Using thiserror's attribute syntax for convenience, the contents of the error type should appear either in the message:

#[derive(Error)]
enum Error {
    #[error("Failed to read settings: {0}")]
    ReadSettings(io::Error),
}

or as a source:

#[derive(Error)]
pub enum Error {
    #[error("Failed to read settings")]
    ReadSettings(#[source] io::Error),
}

but never both. Whatever error in the tonic crate was printing as "error trying to connect: ..." was doing this wrong.

For error layers that have no useful information to add beyond the inner error value that they contain, the approach that I have found best is for the outer error's Display impl and source method to both forward to the inner error's. Thiserror implements this as:

#[derive(Error)]
pub enum Error {
    #[error("Failed to read settings")]
    ReadSettings(#[source] io::Error),

    // Anything else might have failed, we have nothing
    // useful to add at this level.
    #[error(transparent)]
    Other(anyhow::Error),
}

This is better than "{0}" (which would cause the message to be repeated in the cause chain, as above) or something pointless like "Something else failed" or "Internal error" which has no benefit being in the cause chain.

10 Likes