Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conflicting ergonomics for source chains #214

Closed
Ralith opened this issue Dec 22, 2022 · 4 comments
Closed

Conflicting ergonomics for source chains #214

Ralith opened this issue Dec 22, 2022 · 4 comments

Comments

@Ralith
Copy link

Ralith commented Dec 22, 2022

For errors defined by context information and an inner source, we usually want to display both the context and the source to the user. thiserror makes this convenient through annotations like #[error("{context}: {source}")]. However, this pattern will lead to ugly and confusing duplication of information when combined with anyhow's handy feature to format source chains automatically. On the other hand, using something like #[error("{context}")] leads to worse behavior for users who invoke the error's Display impl directly rather than going through anyhow.

anyhow is very popular, but it is also reasonable to expect crucial details to be Displayable without requiring custom traversal code. How can we resolve this conflict?

One idea is for thiserror itself to provide a convenient interface for traversing source chains. Maybe this could be an inherent method, or exposed through the alternate Display mode ({:#}) as done by anyhow, giving consistent Display behavior across both crates.

@Ralith
Copy link
Author

Ralith commented Dec 22, 2022

I attempted implementing {:#} support by modifying the generated Display::fmt body to be

                    let result: std::fmt::Result = { #body };
                    if let e@Err(_) = result {
                        return e;
                    }
                    if __formatter.alternate() {
                        let mut next = <Self as std::error::Error>::source(self);
                        while let Some(source) = next {
                            write!(__formatter, ": {}", source)?;
                            next = source.source();
                        }
                    }
                    Ok(())

but that fails for this test case:

#[derive(Error, Debug)]
#[error(transparent)]
pub struct StructTransparentGeneric<E>(E);

because Self isn't necessarily Error. Adding Self: Error to the where clause of the Display impl seems to cause additional problems. Maybe this could be mitigated by inlining a copy of the generated source method (if any). Thoughts?

@MarijnS95
Copy link

This is the main reason why I've started returning anyhow out of fn main() instead of a custom thiserror structure: Rust invokes the Debug (not Display) implementation which intentionally prints a useful source chain "backtrace": https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations.

For this to make sense though I've also had to clean up stray #[error("{context}: {source}")] for the ugly confusing duplication reason you mentioned; meaning source context is lost in the event that .unwrap() gets called on a thiserror (Which also calls Debug).

So, while at it, is it possible/reasonable to look into providing a similar Debug implementation to anyhow in addition to this alternate Display format?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=232a009a895d8d41d18d26e0056a55b9

@MarijnS95
Copy link

Looks like there are more issues open on the topic of merge ergonomic/consistent source chain formatting (especially when considering thiserror and anyhow being complementary):

@dtolnay
Copy link
Owner

dtolnay commented Feb 17, 2023

Closing in favor of #98.

@dtolnay dtolnay closed this as completed Feb 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants