-
Notifications
You must be signed in to change notification settings - Fork 18
Guidelines for implementing Display and Error::source for library errors #27
Comments
Including relevant references, as they already appear to cover most of the concerns presented in this issue: #23 rust-lang/api-guidelines#210.
The main problem is that there isn't a well established error reporter or a consensus on how they should be reported across the ecosystem. Error reporting isn't a fully solved problem, and libraries or applications without a revamped error reporter with error chain awareness would suddenly provide subpar messages if their dependencies began expecting them.
That sounds reasonable, and could be independently proposed as an addition to the C-GOOD-ERR guideline.
As per rust-lang/api-guidelines#210, that recommendation was already proposed but was put on hold mostly for the reasons stated in point 1 and in the issues linked. |
I don't think this necessarily needs to reference the reporting side, just provide guidance on when and how you should implement |
A fundamental question in my head is: should library authors expect that end users will see source error messages? If so, it encourages layering just a bit of extra context in each If not, it nudges library authors towards including information about lower-level The former leads to subpar UX when applications don't expose source messages ( Since today libraries make different assumptions it means application developers have to either settle for crummy error messages in places or write distinct message formatting code for each individual error (which lessens the appeal of libraries like |
I guess my previous comment is what #23 is about. Apologies for not clicking through before writing it up. |
I actually think this is relevant in a wider context than #23. In my experience, there are different dimensions to
In my experiments with #23 provides guidance on |
The problem is that the guidance we would give depends on how we decide to officially conceptualize the role of the If we decide to prefer the former viewpoint then we would want to prioritize not duplicating information in the reporting interfaces of the If we decide to prefer the latter then we would want to prioritize exposing every error type in the chain of errors to ensure users can always correctly downcast to specific error variants they want to handle. In this situation the guidance would be: Always return source errors via My feeling is that the former viewpoint, that the I've been thinking on this specific issue a lot recently and my feeling is that if we can find some way to unify error reporting so that it is consistently defined for an entire application then we can pretty happily move forward with the former viewpoint and it should work well for everyone. I've some more thoughts on globally consistent error reporting here. However, if we can't fix the holes around reporting for the |
Follow up on this issue and #23I talked with the libs team today about the primary question outlined above (Is the Error trait primarily an interface for reporting errors or primarily an interface for reacting to specific type erased errors?). We came to the consensus that the primary role of the error trait is providing a structured access to error context for the purpose of reporting, so our guidance is officially decided.
As part of this we've also green-lighted efforts to patch holes in error reporting for error trait objects, particularly with how they interact with panics. I expect our immediate next step will be to add an API like |
I think you meant to write |
Hi, I've read the above discussion, and I still think that the error source should be included in the error's message. This is a typical code flow for rpc services: // in sdk B
#[derive(thiserror::Error)]
pub enum BError {
#[error("reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("hyper error: {0}")]
HyperError(#[from] hyper::Error),
}
pub async fn do_something() -> Result<SomeType, BError> {}
// in sdk A
#[derive(thiserror::Error)]
pub enum AError {
#[error("B error: {0}")]
BError(#[from] b::BError),
#[error("C error: {0}")]
CError(#[from] c::CError),
}
pub async fn do_something_further() -> Result<(), AError> {
let b_ret = b::do_something().await.map_err(|e| {
tracing::error!("[A] do_something failed: {e}"); // print a log using tracing here
e.into()
})?;
...
}
// in the server handler
pub async fn handler(req: Request) -> Result<Response, some_framework::ServerError> {
let a_ret = a::do_something_further().await.map_err(|e| {
tracing::error!("[handler] do_something_further failed: {e}"); // print a log using tracing here
some_framework::ServerError::new_biz_error(e) // assume we can convert a::AError to the framework defined ServerError
})?;
} In this case we need to print an error log whenever we get an error, and in the server handler we still need to make it strongly typed to send it back to the client side, so we cannot simply use something like Also, I don't think code like this is reasonable: pub async fn handler(req: Request) -> Result<Response, some_framework::ServerError> {
let a_ret = a::do_something_further().await.map_err(|e| {
tracing::error!("[handler] do_something_further failed: {}", anyhow::anyhow!(e)); // creates an anyhow error or some other reporter whenever we need to print it in tracing
...
})?;
} Furthermore, I don't think force users to use a reporter which will recursively prints the source is reasonable, this is too complexed and need users to know the detailed design of println!("xxx failed: {e}");
Example like this maybe is a little noisy, but it's much easier to debug since it provides a lot of useful information. |
Here is another argument for adding the source. We missed a lot of information about errors because they were formatted/included using |
I sometimes wonder if the entire Like I'm 100% of the opinion that sources should not be included by default in Display, because that's the trait you use for chaining individual messages together in the first place. But that means you absolutely have to do that or you miss out on important parts of the error as shown above. So you both need the functionality of "give me this individual message" and "give me the entire error", while also possibly needing localization and maybe more details as part of debug printing. The current design just doesn't support what you would need at all. |
I share this thought. It moving to core also means the Hypothetically if the advice would be to always print all the error detail, i.e. the source, then (I'm saying this as someone who always implemented the |
There is no way we are deprecating the As for backtrace support, see: rust-lang/rust#103765 (comment) The problem here is real though, and I struggle with it myself. But that doesn't mean we ought to throw everything out and start over. |
For example, if |
There is an unstable type for printing error-chains: |
@Nemo157 Yeah I forgot about that. I think pushing that towards stabilization will do a lot to help this particular problem. |
I don't think |
iirc |
And to be clear, I was careful in my wording above: I didn't claim |
Thanks for this reminder! Is this a new syntax that is not stable?
If all errors are supposed to use |
I mean, maybe we can just change |
The chain printer could filter out duplicated messages; |
While I changed hyper and reqwest's error types to follow this pattern, I do still feel like it was a downgrade for users who don't know about reporters (which I posit is most users). This is why I proposed inverting it all, and making the default more informative and making you opt-in to finer control: #53 (explored in https://github.com/seanmonstar/errors). A couple years later, and I still feel: informative by default is more user-driven, reporters are for advanced users. I want defaults that help the most people, advanced users can figure out how to do other things. For example, here's references to users being confused by the default showing them less: |
I took a look at that crate, seems that the filter may lead to some loss of performance? I agree with @seanmonstar , reporters should be for advanced users, and the defaults should help the most people
In most cases, users just want to get the most detailed message which is analyzed by themselves to figure out the reason, and in these cases the innermost cause is likely the most helpful one. |
I wonder if there's a way (maybe with specialization and a newtype in |
Making If we follow this reasoning, it'd make more sense for Notwithstanding the above, I still think |
I have been involved in trying to fix a couple of crates errors to work better with error reporters like
anyhow
/eyre
recently (clap-rs/clap#2151, sunng87/handlebars-rust#398). One big issue I find with reporting these sorts of issues is not having a succinct, canonical "this is how errors should be implemented" source to link to. The only api-guideline related is C-GOOD-ERR which doesn't talk about chaining at all, and simply says:It would be very useful to have something to link to which talks about chaining to sources, not adding unnecessary "Error: " prefixes, and not printing your
source
's message as part of your own message. (I'm assuming a chapter/appendix in the error-book, or a new section in the api-guidelines).The text was updated successfully, but these errors were encountered: