Prefer inherent over supertrait methods in trait object method resolution#158320
Prefer inherent over supertrait methods in trait object method resolution#158320Jules-Bertholet wants to merge 4 commits into
Conversation
b4baa8f to
4b8693c
Compare
| let x: &(dyn T + Send) = &0i32; | ||
| assert_eq!(x.foo(), 0); |
There was a problem hiding this comment.
This part is not so nice… ideally this would use the inherent impl too.
There was a problem hiding this comment.
I looked into making this work, but it seems non-trivial. The inherent_impls query is based on DefIds, so information about auto traits is lost
|
@bors try @rust-timer queue and cratering it when the try job finishes, impl lgtm |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Prefer inherent methods in trait object method resolution
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (8a2f6fe): comparison URL. Overall result: no relevant changes - no action neededBenchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up. @rustbot label: -S-waiting-on-perf -perf-regression Instruction countThis perf run didn't have relevant results for this metric. Max RSS (memory usage)Results (primary -5.9%, secondary -6.5%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (secondary 3.9%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 510.446s -> 505.256s (-1.02%) |
|
@craterbot check |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🎉 Experiment
Footnotes
|
|
The reference has a warning about this that needs to be corrected if this PR were to go through. |
|
Was there any specific reason you've working on this. I agree this feels vaguely desirable, but don't have a concrete use case in mind right now. There are a few interesting cases here 🤔 Which method to use already depends on the self type trait Trait<T> {
fn func(&mut self) {
println!("trait");
}
}
impl<T> Trait<T> for i32 {}
impl dyn Trait<u32> {
fn func(&self) {
println!("inherent");
}
}
fn foo(mut x: Box<dyn Trait<u32>>) {
x.func(); // inherent
(&mut *x).func(); // trait
}
fn main() {
foo(Box::new(1));
}This candidate preference guides inference trait Trait<T> {
fn func(&self) {
println!("trait");
}
}
impl<T> Trait<T> for i32 {}
impl dyn Trait<u32> {
fn func(&self) {
println!("inherent");
}
}
fn foo() {
let x: Box<dyn Trait<_>> = Box::new(1);
x.func(); // ambiguity error -> now constrains the infer var to `u32`
}
fn main() {
foo();
}Both of these feel acceptable to me and aren't really new issues @rfcbot reviewed Also, while I consider this (and want it to be) a bit more Types than Lang, this has definitely a stronger Lang overlap @rust-lang/lang |
|
So, in the lang meeting, I expressed some skepticism of this change. (Also, I agree this is at the intersection of lang + types.) My sense is that the language already says that mod bar {
pub trait Test {
fn test(&self);
}
impl Test for () {
fn test(&self) {
println!("Test");
}
}
}
pub trait AnotherTest {
fn test(&self);
}
impl<T: ?Sized> AnotherTest for T {
fn test(&self) {
println!("AnotherTest");
}
}
fn example(d: &dyn bar::Test) {
// First, `Test` is not imported, so this trait method is not callable, but second, we wind up `d.test()`
// not `AnotherTest::test`
d.test();
AnotherTest::test(d); // prints "AnotherTest"
}
fn main() {
example(&()); // prints Test::test
}I think the bug is that we permit something like this in the first place: impl dyn Foo {
fn test(&self);
}In general, I regret permitting inherent methods on This also impacts the project for for async fn in dyn trait -- cc @jackh726 and @spastorino -- in that part of the plan there is take advantage of the fact that So my proposal would be: Let's make it a hard error to define an inherent method on |
|
@nikomatsakis I don't understand what you're saying about "primary traits". When we define a method on |
|
@nikomatsakis One pattern that's useful is that people sometimes define an inherent method with |
There's one case this doesn't address: what if the trait method is on trait Super {
fn method(&self) {}
}
trait Sub: Super {}
impl<'a> dyn Sub +'a {
fn method(&self) {}
}
fn foo(a: &dyn Sub) {
a.method();
}Currently, this gives an error. After this PR, it compiles, with the method call resolving to the inherent method. And we want it to compile, because |
|
@rfcbot fcp cancel |
|
@oli-obk proposal cancelled. |
|
@rfcbot fcp merge lang,types |
|
@oli-obk has proposed to merge this. The next step is review by the rest of the tagged team members:
No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
|
As for my original motivation for working on this: I had a forlorn (and now dashed) hope that this might help fix #57893. TL;DR of that issue: The compiler currently doesn't enforce coherence between an explicit My forlorn hope was that maybe we could make While the |
Hmm, I don't consider this a strong argument against this change. Having weird priorities and import behavior for trait objects and generic parameters feels unrelated to whether a method is inherent: e.g. the following behaves in the same way mod bar {
pub trait Test {
fn test(&self) {
println!("Test");
}
}
impl Test for () {}
}
pub trait AnotherTest {
fn test(&self) {
println!("AnotherTest");
}
}
impl<T: ?Sized> AnotherTest for T {}
fn example<T: bar::Test>(d: &T) {
T::test(d); // prints Test::test
AnotherTest::test(d); // prints "AnotherTest"
}
fn main() {
example(&());
}
I did think about this when looking at this PR (and didn't actually mention that in my review 😅). I think having 3 preference levels is fine. What is concerning to me is that "
one way to deal with the overlap of My vibe here is that the concern by Niko about future language work related to non dyn-compatible trait objects together with the fact that the reasoning to land this change being fairly weak means that I don't really see us merging this change. The effort to judge whether this change negatively interacts with future work is larger than the benefit we get from landing it 🤔 On a more general note, I really dislike the current state of dyn trait being quite broken since 1.0 while never being quite important enough for somebody to sit down and spend a few months really diving into where to go long term. I don't even have anything actionable here. I expect that worst case I will look into this for #57893 once a bunch of the easier and "more core" unsoundnesses are fixed |
That's fair. I think it would be desirable to have a way to refer to the vtable method specifically, but makes sense to block until that is figured out. Would you be receptive to a more restricted PR that only prefers inherent methods over methods from supertraits? This would be sufficient to fix the semver hazard issue, and wouldn't cause any issues for non dyn-compatible trait objects because you can always do an upcast. |
This is kind of my point. Normally we permit 'inherent' things only on "nominal types" like struct or enum that can't be "added". The "name" for a dyn trait is kind of... the combination of names of all the traits... it's just all a bit odd. I maintain this is because a
This is a good point, it does lead to a "fragile base class" type of problem, or it could anyway. That said, we can certainly permit inherent methods defined on
I wondered if you would bring it up. It's true that generic parameters also add the trait methods as "pseudo-inherent methods". I wouldn't mind reverting that change in a future edition, to be honest, although also it'd just be annoying for users for little real gain. I feel differently about dyn. I guess it's because of the direction that I think we are going to make |
This is now implemented. (The Reference PR will still need updating.) |
View all comments
Partially addresses #51402. Inherent methods on trait objects now take precedence over methods from supertraits of the trait object's trait; these last continue to take precedence over other trait impls as before.
I don't know whether this should be feature gated or insta-stable FCP, will let T-types decide that.
r? types
@rustbot label T-types A-dyn-trait