-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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 provider API to error trait #98072
Conversation
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
r? @kennytm (rust-highfive has picked a reviewer for you, use r? to override) |
@rustbot label +T-libs-api -T-libs |
This comment has been minimized.
This comment has been minimized.
/// fn provide<'a>(&'a self, req: &mut Demand<'a>) { | ||
/// req | ||
/// .provide_ref::<MyBacktrace>(&self.backtrace) | ||
/// .provide_ref::<dyn std::error::Error + 'static>(&self.source); |
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.
Do you foresee people doing this? I'd probably discourage it as it's replicating Error::source
.
I could also see people doing .provide_ref::<SourceError>
as a way of getting the source error and downcasting it in one step instead of two.
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.
Do you foresee people doing this? I'd probably discourage it as it's replicating
Error::source
.
In the original PoC impl of error in core I thought it might be a good idea to deduplicate all of the provide style APIs to encourage a single point of interaction with the error trait, we could implement source
in terms of provide
by default, here's the old version of that:
fn source(&self) -> Option<&(dyn Error + 'static)> {
core::any::request_by_type_tag::<'_, core::any::tags::Ref<dyn Error + 'static>, _>(self)
}
So you'd still default to using source
as the getter but you could implement it via either method. I'm not sure if this is a good idea but I think it's worth considering. The biggest concern I can think of is that it will be easy to provide the source as the wrong type, similar to what you mentioned, someone could be trying to provide a trait object but forget to add the type parameter field and just write .provide_ref(&self.source)
. This could be improved a bit by adding an extra helper on Demand
for .provide_source
which is hard coded to dyn Error
, but that seems a bit hacky given Demand
will probably see usage in APIs unrelated to the error trait.
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.
I don't really see a problem with providing it via both, although it's somewhat odd. I think you might want to provide it as the concrete type in more cases? Regardless, I think landing this will give a chance for idioms to settle.
/// impl std::error::Error for Error { | ||
/// fn provide<'a>(&'a self, req: &mut Demand<'a>) { | ||
/// req | ||
/// .provide_ref::<MyBacktrace>(&self.backtrace) |
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.
This turbofish is redundant, I believe, so I'd recommend removing it.
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.
The examples all use turbofish unconditionally to reduce the chance of accidentally erasing the member as an unexpected type, with the turbofish you'll get a compiler error if you don't provide that exact type.
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.
accidentally erasing the member as an unexpected type
How would that occur? My understanding is that without the turbofish, the compiler will only be able to use the concrete type of the argument for type inference.
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.
How would that occur? My understanding is that without the turbofish, the compiler will only be able to use the concrete type of the argument for type inference.
by providing a type that is different from what you intended / expected, the example I gave in the other comment on source is the best example I have off of the top of my head. If you mean to provide something as a trait object but you don't turbofish the trait object type or remember to manually coerce it you'll end up providing the original unerased type, and then any attempt to request the trait object will fail at runtime.
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'll end up providing the original unerased type
Right, but this is "accidentally forgetting to erase" while you said (emphasis mine)
accidentally erasing the member as an unexpected type
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.
aah, yea, I overused erasing. In the first comment I was referring to .provide_ref
as if it erased the T
that is being provided, but in reality its the Demand
that is the type erased memory location where the T
is stored, and provide_ref
is acting more like downcast
on Any
.
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.
When I've played around with this API, I've always used explicit turbofish, and think it's a good practice so that you know what you're providing.
/// let backtrace = MyBacktrace::new(); | ||
/// let source = SourceError {}; | ||
/// let error = Error { source, backtrace }; | ||
/// let dyn_error = &error as &dyn std::error::Error; |
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 the choice of dyn Error
here? Perhaps the idea is to demonstrate that it works on concrete types and trait objects? If so, I might encourage doing that more explicitly:
let a = error.request_ref::<MyBacktrace>().unwrap();
assert!(core::ptr::eq(&error.backtrace, a));
let dyn_error = &error as &dyn std::error::Error;
let b = dyn_error request_ref::<MyBacktrace>().unwrap();
assert!(core::ptr::eq(a, b));
or can you not call request_ref on a concrete type? If so, that'd make me sad.
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.
or can you not call request_ref on a concrete type? If so, that'd make me sad.
Not until we move error into core sadly, for now I had to impl the Provider trait directly on dyn Error
in order to make coherence treat it as a foreign trait on a local type. impl<E: Error> Provider for E
produces a compiler error complaining about a blanket impl on a foreign trait.
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.
Not until we move error into core sadly
OK, if it can happen then, I'll be happy.
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
r? @thomcc |
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.
This looks great to me! No real issues to speak of, and can't wait to start playing with it.
/// impl std::error::Error for Error { | ||
/// fn provide<'a>(&'a self, req: &mut Demand<'a>) { | ||
/// req | ||
/// .provide_ref::<MyBacktrace>(&self.backtrace) |
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.
When I've played around with this API, I've always used explicit turbofish, and think it's a good practice so that you know what you're providing.
/// fn provide<'a>(&'a self, req: &mut Demand<'a>) { | ||
/// req | ||
/// .provide_ref::<MyBacktrace>(&self.backtrace) | ||
/// .provide_ref::<dyn std::error::Error + 'static>(&self.source); |
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.
I don't really see a problem with providing it via both, although it's somewhat odd. I think you might want to provide it as the concrete type in more cases? Regardless, I think landing this will give a chance for idioms to settle.
@bors r+ |
Rollup of 6 pull requests Successful merges: - rust-lang#98072 (Add provider API to error trait) - rust-lang#98580 (Emit warning when named arguments are used positionally in format) - rust-lang#99000 (Move abstract const to middle) - rust-lang#99192 (Fix spans for asm diagnostics) - rust-lang#99222 (Better error message for generic_const_exprs inference failure) - rust-lang#99236 (solaris: unbreak build on native platform) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
…plett add tracking issue to generic member access APIs Missed as part of rust-lang#98072
add tracking issue to generic member access APIs Missed as part of rust-lang/rust#98072
Implements rust-lang/rfcs#2895