alternate `Display` to include the source chain
Hi, and thanks for your work on this great library!
I'm considering if it's possible to support displaying the source chain with alternate Display (i.e. {:#}), which seems to be a convention for error reporting utilized in many other error handling libraries, including anyhow and error-stack.
- https://docs.rs/anyhow/1.0.72/src/anyhow/fmt.rs.html#10-14
- https://docs.rs/error-stack/0.3.1/src/error_stack/fmt.rs.html#1014-1019
Yeah, it seems Report has a similar idea. However, I have several concerns...
- I'm not sure if developers always remember to wrap the
ErrorintoReportbefore printing them out, or they'll lose the information from the sources if{source}is not included in#[snafu(display(..))](which I guess should be a good practice?). - It looks like
Reportalways includes the backtrace if provided, which might not be suitable to appear in a user-facing error message. As a result, we have to introduce our ownDisplaywrapper.
I'm not sure if developers always remember
How would this be different from remembering to use the alternate display flag?
which I guess should be a good practice
The current best practice is to do exactly one of:
- include the source in the
Displayimplementation - link to the source via
Error::source
Reportalways includes the backtrace if provided
Right now, it only happens on nightly Rust if you've enabled the unstable-provider-api feature flag, but at some point in the future it might happen by default. We might also add some small configuration knobs.
utilized in many other error handling libraries
I believe the big difference is that these other libraries (and I'm not an expert on them) don't actually give you control over the implementation of Display in the first place, while SNAFU does. For example, could you share your desired syntax to specify the regular and alternate Display implementations for a SNAFU-enhanced error?
We have had some discussions about some kind of #[snafu(display(false))] style option that would disable the creation of the Display implementation, letting the user define it completely by hand. That would allow uncommon / highly special cases without completely jettisoning SNAFU.
We have had some discussions about some kind of
#[snafu(display(false))]style option that would disable the creation of theDisplayimplementation, letting the user define it completely by hand. That would allow uncommon / highly special cases without completely jettisoning SNAFU.
Linking #473, as more people have suggested this feature.
I'm running into this now as well, after realizing that the reason we didn't get good error logs in iroh was because we weren't wrapping our errors in our custom error type. We did however not forget to use the alternate display. I think it's fairly intuitive to expect the alternate display to be a more elaborate, human-readable error print.
What's the danger of implementing alternate display to show sources? Currently the normal and alternate display do the same thing, and people expect the alternate display to show sources intuitively.
I suppose the main clash comes from SNAFU having a dedicated type for error reporting, rather than choosing to combine both handling and reporting into the constructed error types. While admittedly both concerns (and others such as adding context) are important, note that there isn't exactly a standard or convention on how to do this: anyhow offers an alternate display implementation via {:#}, but errors created via thiserror do not. eyre also supports {:#}, but it also takes one step further on this by including the EyreHandler trait to let consumers build their own display formats. I suppose people only expect it because they are specifically using a lot of anyhow and/or eyre.
The two non-exclusive ways I can see right now that would probably help are:
- a) With SNAFU, consumers have an opinionated
Reportwrapper for error reporting. If they don't like any of the forms provided byReport, they need to write their own wrapper. We could probably offer some construct to help build custom report wrappers that work as drop-in replacements. - b) Add the
alternate()clause to allDisplayimpls for generated errors, with the documented notice that wrapping the error withReportis preferred. The problem I see here is that we'd be generating more code for a use case that is not exactly recommended.
If there is a proof-of-concept that shows some form of error display which cannot be done using any constructs from SNAFU, that could be an assessment worth making.
a) With SNAFU, consumers have an opinionated Report wrapper for error reporting. If they don't like any of the forms provided by Report, they need to write their own wrapper. We could probably offer some construct to help build custom report wrappers that work as drop-in replacements.
We have our own wrapper in n0_snafu.
This is the code in question where we're logging a #[derive(Snafu)] error:
tracing::warn!("probe failed: {:#}", err);
Err(probes_error::ProbeFailureSnafu {}.into_error(err))
We can implement an alternate display for our own wrapper, but then this code wouldn't compile:
tracing::warn!("probe failed: {:#}", n0_snafu::Error::from(err));
Err(probes_error::ProbeFailureSnafu {}.into_error(err))
Because n0_snafu::Error owns err.
Now, there are workarounds:
- Make your error clonable (this cannot always be done)
- Add a lifetime to
n0_snafu::Error(this seems very untypical and unergonomic) - Add a separate wrapper that implements our desired
Displaylogic
We'll always be able to fix our issue by doing the last thing, but it seems wasteful and error prone, given snafu errors already implement Display.