Prefer inherent methods in trait object method resolution#158320
Prefer inherent methods in trait object method resolution#158320Jules-Bertholet wants to merge 3 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. |
Stabilization reportThis PR allows calling an inherent method on a trait object that shares a name with the method's primary trait. Up to now, this has always failed with an ambiguity error; after this PR, the inherent method shadows the trait method. Reference PR: rust-lang/reference#2308 What is stabilizedtrait Trait {
fn foo(&self) -> i32 {
1
}
}
impl Trait for i32 {}
impl<'a> dyn Trait + 'a {
fn foo(&self) -> i32 {
2
}
}
fn main() {
let a: &i32 = &3;
assert_eq!(a.foo(), 1);
let obj: &(dyn Trait) = a;
assert_eq!(obj.foo(), 2); // would previously give ambiguity error
assert_eq!(Trait::foo(obj), 1);
}What isn't stabilizedInherent method lookup continues to not peel off auto traits on trait objects. Continuing our previous example: let obj2: &(dyn Trait + Send) = a;
assert_eq!(obj2.foo(), 1); // uses trait methodThere is a risk that merging this PR now could make this more difficult to fix in the future, because doing so would change the behavior of code like the example above. |
|
@rfcbot fcp merge 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! See this document for info about what commands tagged team members can give me. |
|
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 |
View all comments
Fixes #51402. Inherent methods on trait objects now take precedence over methods from the trait object's trait, but 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