-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Add downcasting to std::error::Error #24793
Conversation
⌛ Testing commit e912b2a with merge 0ffa676... |
💔 Test failed - auto-linux-64-opt |
@aturon is it really necessary to mark EDIT: Note that the deprecation path you describe wouldn't be forwards compatible, since downstream users could add negative implementations of Reflect to their own types if we expose it. |
#[unstable(feature = "core", | ||
reason = "unclear whether to commit to this public implementation detail")] | ||
fn type_id(&self) -> TypeId where Self: 'static { | ||
TypeId::of::<&'static Self>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not TypeId::of::<Self>
? TypeId
supports T: ?Sized
Should we also add by-value downcasting from |
@reem The need for re: forward compatibility of the deprecation path -- good point. This will require blanket impls and OIBIT to coexist. But that seems plausible (cc @nikomatsakis). I suppose the main alternative to this change, which we discussed on IRC, would be to wait until upcasting it working and then ask people to use e.g. FYI: I also updated with your other suggestions. Thanks! @alexcrichton @nikomatsakis thoughts? |
#[doc(hidden)] | ||
#[unstable(feature = "core", | ||
reason = "unclear whether to commit to this public implementation detail")] | ||
fn type_id(&self) -> TypeId where Self: 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move the Reflect bound to a method-level where clause here, then we can use exclusively Any and avoid stabilizing Reflect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, actually this will prevent downcasting with Box<Error>
, rather than Box<Error + Reflect>
, which iirc isn't even allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drat, it was an interesting idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about this some more, and we can get away without stabilizing Reflect
with just a bit less flexibility - instead of implementing Error for T: Reflect
in e.g. TryLockError, just implement for T: Any
. It's slightly less flexible, but it's backwards compatible to generalize the impls when/if we do stabilize Reflect
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@reem Ah yes, I think at some point in the iterations on this topic I confused myself into thinking this would hurt more than it does: you can still have non-'static
generic error types, they will just fail to be Error
for the time being. That seems plausible, and I agree that not stabilizing Reflect
is a good idea.
triage: beta-nominated Because @alexcrichton said so above. (Not r+'ing though since @aturon is awaiting feedback ) |
@aturon Back in the RFC thread you appeared to agree with me that dynamically typed error-downcasting is an "anti-pattern" (one should use |
@aturon I think after some more discussion we agreed on not stabilizing The addition of downcasting here is one of the pillars of |
There's also the matter that you can't actually do Error + Any. Any is kind
|
@glaebhoerl Continuing along the lines of what @alexcrichton said: it's still the case that enums are the recommended, conventional way to provide concrete error types, and should be preferred to downcasting when practical. That said, there are places where downcasting is appropriate. One important example that @alexcrichton is the Now, I've long maintained that such use cases are important, but why build downcasting into the With all of that said, one of the primary use cases of |
triage: beta-accepted |
@aturon Thanks for explaining! An associated error type was indeed the alternative solution I had in mind, but if that was tried and proved untenable, then I guess that's that. I'm mostly concerned because dynamically downcasting exception types happens to be the idiomatic error handling solution in a large number of very popular languages, and it would be very easy to see that become the done thing in Rust as well if we're not watchful. I guess we'll have to make sure the documentation is clear about this. :) |
a010bf6
to
6436123
Compare
@alexcrichton @reem Sorry for the delay here, been in intense meeting mode this week. I've pushed an update which leaves |
@@ -222,7 +220,7 @@ impl TypeId { | |||
/// Returns the `TypeId` of the type this generic function has been | |||
/// instantiated with | |||
#[stable(feature = "rust1", since = "1.0.0")] | |||
pub fn of<T: ?Sized + Any>() -> TypeId { | |||
pub fn of<T: ?Sized + Reflect + 'static>() -> TypeId { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to just use Any here, the docs will be clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to use Reflect
here to allow Error
to implement downcasting without inheriting from Any
.
r=me with a few final nits, thanks @aturon! |
|
||
#[unstable(feature = "error_downcast", reason = "recently added")] | ||
/// An extension trait for downcasting from boxed Errors | ||
pub trait BoxErrorExt { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we have a method on Error which takes self: Box?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, we can indeed! I sometimes forget we still have that special treatment of Box
.
This commit brings the `Error` trait in line with the [Error interoperation RFC](rust-lang/rfcs#201) by adding downcasting, which has long been intended. This change means that for any `Error` trait objects that are `'static`, you can downcast to concrete error types. To make this work, it is necessary for `Error` to inherit from `Reflect` (which is currently used to mark concrete types as "permitted for reflection, aka downcasting"). This is a breaking change: it means that impls like ```rust impl<T> Error for MyErrorType<T> { ... } ``` must change to something like ```rust impl<T: Reflect> Error for MyErrorType<T> { ... } ``` except that `Reflect` is currently unstable (and should remain so for the time being). For now, code can instead bound by `Any`: ```rust impl<T: Any> Error for MyErrorType<T> { ... } ``` which *is* stable and has `Reflect` as a super trait. The downside is that this imposes a `'static` constraint, but that only constrains *when* `Error` is implemented -- it does not actually constrain the types that can implement `Error`. [breaking-change]
@reem: FYI, I've incorporated most of your suggestions, except for those that are not possible. |
@bors: r=alexcrichton p=10 Prioritizing due to breakage. |
📌 Commit a576262 has been approved by |
This commit brings the `Error` trait in line with the [Error interoperation RFC](rust-lang/rfcs#201) by adding downcasting, which has long been intended. This change means that for any `Error` trait objects that are `'static`, you can downcast to concrete error types. To make this work, it is necessary for `Error` to inherit from `Reflect` (which is currently used to mark concrete types as "permitted for reflection, aka downcasting"). This is a breaking change: it means that impls like ```rust impl<T> Error for MyErrorType<T> { ... } ``` must change to ```rust impl<T: Reflect> Error for MyErrorType<T> { ... } ``` This commit furthermore marks `Reflect` as stable, since we are already essentially committed to it via `Any`. Note that in the future, if we determine that the parametricity aspects of `Reflect` are not needed, we can deprecate the trait and provide a blanket implementation for it for *all* types (rather than by using OIBIT), which would allow all mentions of `Reflect` to be dropped over time. So there is not a strong commitment here. [breaking-change] r? @alexcrichton
This commit brings the
Error
trait in line with the Error interoperationRFC by adding downcasting,
which has long been intended. This change means that for any
Error
trait objects that are
'static
, you can downcast to concrete errortypes.
To make this work, it is necessary for
Error
to inherit fromReflect
(which is currently used to mark concrete types as "permittedfor reflection, aka downcasting"). This is a breaking change: it means
that impls like
must change to
This commit furthermore marks
Reflect
as stable, since we are alreadyessentially committed to it via
Any
. Note that in the future, if wedetermine that the parametricity aspects of
Reflect
are not needed, wecan deprecate the trait and provide a blanket implementation for it
for all types (rather than by using OIBIT), which would allow all
mentions of
Reflect
to be dropped over time. So there is not a strongcommitment here.
[breaking-change]
r? @alexcrichton