rust-lang / project-error-handling

Error handling project group
Apache License 2.0
268 stars 18 forks source link

Should recovering the `type_name` of a `dyn Error` be supported? #52

Open thomcc opened 2 years ago

thomcc commented 2 years ago

It would be nice (IMO) if there were a way to recover the type name (as produced by core::any::type_name[^1]) of a dyn Error. I've wanted this several times, as in many cases, it can be useful as a summary of what went wrong at a fairly high level, and without any details (since the details are available through other means, such as the Debug/Display implementations).

(A concrete use case I've recently hit involves serializing Error instances for remote reporting, where the type name could be useful for broad categorization. That said, for my case it is not a problem to capture this information when the error is generated, before its type is erased. So... I don't know how compelling this example is)

On the other hand, perhaps this isn't worth having. In principal, enough information to figure out what type the error initially had should probably be present in the Debug output. And it may be too similar to Error::description(), which we deprecated -- presumably (I don't actually know the reason) for being inflexible and redundant, which are arguably flaws this would have as well. Also, the fact that I was able to capture this prior to type erasure without much pain indicates that perhaps this would be true for most other situations where it's needed.

If we do want it, I think it would be straightforward to implement -- I'm imagining that this would just be a type_name(&self) -> &'static str on the error trait, and would have an implementation similar to that of Error::type_id. I think it would have the same non-guarantees that core::any::type_name has for consistency, accuracy, etc. (Also, while I don't see a reason to allow users to override it, I don't really see a reason to forbid it either -- It's not like anybody can trust it being accurate or consistent, after all). That said, I don't feel very strongly about these implementation details -- you certainly could design it other ways (including repurposing Error::description(), although this would certainly not be my suggestion).

(P.S. Sorry if this has been discussed, I didn't see it)

[^1]: Note that while core::any::type_name_of_val exists and seems like it would support trait objects, it doesn't (at least, not really -- it essentially just reports dyn std::error::Error). I believe this is intentional, and that it probably will due to the nontrivial binary size cost this would incur, even for code that doesn't use it.

This is discussed in [the `type_name_of_val` tracking issue](https://github.com/rust-lang/rust/issues/66359), and despite my desire to have type names work for `Error` instances, I don't think I would be in favor of it work on all trait objects; due to said cost.

That said, for errors it makes sense to me, for the reason mentioned above (it can be a useful high-level summary of what went wrong).
yaahc commented 2 years ago

On the other hand, perhaps this isn't worth having. In principal, enough information to figure out what type the error initially had should probably be present in the Debug output. And it may be too similar to Error::description(), which we deprecated -- presumably (I don't actually know the reason) for being inflexible and redundant, which are arguably flaws this would have as well. Also, the fact that I was able to capture this prior to type erasure without much pain indicates that perhaps this would be true for most other situations where it's needed.

My understanding is that description was deprecated because it's signature was to just return a &str, so it didn't allow for runtime context to be included without preformatting the string and holding the backing storage for the str in the error itself. If I could go back and change everything I think I'd make it so that description was fn description(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result and have Display be a blanket impl for all error types that does the common case of printing the error and it's sources on a single line, and possibly use the alt flag to print errors on multiple lines.

(P.S. Sorry if this has been discussed, I didn't see it)

This hasn't been discussed before that I can recall, so you're good.

Is it currently possible to do this for a &dyn Any? I made a basic attempt but was only able to get dyn Any as the type_name. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=3d52f1d764039ed4f50089af1f6d5f09

In my mind, the only reason Error has any of this type erasure machinery is because it isn't possible to have compound vtables like dyn Error + Any. I would like to preserve that equivalence as much as possible, so I wouldn't be in favor of adding this to dyn Error unless similar functionality was adopted for dyn Any.

thomcc commented 2 years ago

Is it currently possible to do this for a &dyn Any? I made a basic attempt but was only able to get dyn Any as the type_name.

No. I suspect supporting this for dyn Any would have a much larger cost in terms of code size than only doing it for error, although it would still be less than supporting it for all trait objects, so perhaps the cost would be low enough not to matter.