-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: calling default trait methods from overriding impls #3329
base: master
Are you sure you want to change the base?
Conversation
since you're not getting the super-type of <MyStruct::super as Trait>::method() imho having the super attached to the method is better: <MyStruct as Trait>::super::method() |
Co-authored-by: campersau <buchholz.bastian@googlemail.com>
It may be worth to explain the interaction with specialization, where you can have multiple default implementations (it can be as simple as "always calls the default of the trait declaration). |
That's a good point; it needs to explain how it interacts with specialization. I don't think it's quite as simple as "always calls the default of the trait declaration", though, because https://rust-lang.github.io/rfcs/1210-impl-specialization.html explicitly says:
so we don't want to treat those differently. We could say something like "super calls always call the method which would have been called if the overriding implementation (and anything that overrides them) were not present". Or a more conservative approach would be to say "super always calls the default of the trait declaration or the default unconstrained impl". That also brings up the question of whether/how to allow calling specialized defaults, but since impls don't have names, that would require something like If we don't (immediately) allow calling specialized default methods with something like UFCS, then that probably forces us to use "super always calls the default of the trait declaration or the default unconstrained impl", because otherwise otherwise, in ambiguous situations, there would be no way to disambiguate. |
In gtk-rs we would also be able to make use of this new feature. Currently we work around this by having the default implementations of traits (e.g. In addition to this specific feature, it would also be great if it would be possible to (reliably) detect (at runtime is sufficient) whether a trait implementation overrides a specific method implementation or if it kept it at the default. |
We could workaround this issue by using a blacket type that implements that default trait. impl Foo for S {
fn foo(&self) {
struct U;
impl Foo for U;
U::foo();
}
} |
…as Trait>::super::foo(self)`
I added detail for how this interacts with specialization. An expansion to universal function call syntax seems necessary to support calling specific impls. I also replaced |
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.
Commenting here from the perspective that I could have used this in a specialization, if it existed.
|
||
Then within a `Vec<String>` impl: | ||
|
||
- These evaluate to "Vec<String>": |
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 doesn't render properly due to the angle brackets being interpreted as HTML. Put them in backticks instead.
- `self.name()` | ||
- `<Vec<String> as Trait>::name(self)` | ||
- `<<T=String> Trait for T>::name(self)` | ||
- These evaluate to "Vec<Display>": |
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.
Ditto
- `<<T=Vec<String>> Trait for T where T: Display>::name(self)` | ||
- These evaluate to "Trait": | ||
- `self.super.super.super.name()` | ||
- `<Vec<String> as Trait>::super::super::super::name(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.
Isn't the super::super::...
chaining a forward compatibility hazard in case another specialization of intermediate specificity is inserted somewhere in the chain?
I think super
shouldn't be seen as a way of traversal but as a namespace disambiguator.
UFCS normally accesses the most-specific-impl namespace. By adding the keyword we signify we want exactly the impl on the named type, not a subtype.
Are there cases where it would be necessary to use super
to vaguely point in a direction because one cannot name the exact supertype?
- These evaluate to "Trait": | ||
- `self.super.super.super.name()` | ||
- `<Vec<String> as Trait>::super::super::super::name(self)` | ||
- `<<T=Vec<String>> Trait for T>::name(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.
I'm not sure I like this syntax. <<T=Struct> Trait for T: Display>::name
looks like a convoluted way to spell <Struct as Trait>::name
, which would call the most specific method, not the trait-level default.
Maybe the impl
keyword could be used to signify that we're calling an impl-provided default (which can only happen via specialization) instead of the trait-provided one?
Then it could be <Self as impl<T: Display> Trait for T>::default::name()
.
Other syntax was considered, such as: | ||
|
||
- `super.method()`: This implicitly refers to `self` while not naming it, which could be surprising. It also makes it difficult to call default implementations on other values of the same type. | ||
- `<super::Trait>::method(self)`: Using `super::` as a prefix could conflict with the existing semantics of `super`. |
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.
Using default
instead super
as keyword might work? It's about calling default impls after all.
If invocation of the default method impl is expected in all impls, why doesn't the crate providing the trait just offer the default method as a separate function? Or, why not just extract the default impl into another method and make the caller cal both traits every time? imo the requirement to call the default impl is an antipattern because we cannot ensure that the user always calls the default impl anyway, which causes statically unchecked bugs. That said, I had a similar requirement in |
Do you think trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
// ...
} => trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>) {
iterator_size_hint_default(self)
}
// ...
}
pub fn iterator_size_hint_default<T: Iterator>(this: T) -> (usize, Option<usize>) {
(0, None)
} |
Pzixel: That is the same point made by SOF3. As described in the RFC: "This [delegating to a free function] requires the trait author to expose the default implementations intentionally." In other words, if the trait author didn't think about it, the user can't call the default implementation. The RFC also reduces boilerplate and namespace pollution. |
@adamcrume I'm not saying that "this RFC is not needed because you can write this", I'm writing this how it can be desugared to not rely on other unstable features like default implementations. |
Another scenario I am implementing The intuitive approach here seems to be the ability to call the parent implementation, but it seems syn might as well just expose |
Rendered