-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Duplicate inherent static methods can be defined in separate impl
blocks
#22889
Comments
triage: I-nominated I think this is fundamentally caused by the following compiling, which seems fairly bad: struct Foo;
impl Foo {
fn id() {}
}
impl Foo {
fn id() {}
}
fn main() {} Putting both |
impl
blocks
So this came up in the context of the specialization RFC rust-lang/rfcs#1210. Initially that RFC tried to rationalize inherent impls as being from an anonymous trait, but that language was pulled. Nonetheless, I (and @aturon) think that this behavior is basically a bug and we should deprecate overlapping inherent impls of this kind. The question is what the precise rules OUGHT to be. Some possibilities:
|
The latter is obviously more backwards compatible, so it'd be good to try and get some crater results. |
While there may be interactions with specialization, inherent methods are more flexible, so even the disjoint rule can be non-trivial, if we ever want argument-type-based-lookup. At least it wouldn't affect existing code with callable methods, as the current lookup implementation would result in ambiguity even if the signatures are disjoint (but |
Yeah, good point. Hopefully impact would be minimal. Still, it's not so hard to have types that are disjoint in practice, but not disjoint enough for the coherence checker -- particularly given the conservative rules imposed by RFC 1023. |
triage: P-high |
In @rust-lang/lang meeting decided to try the more conservative rules and do a crater run to try and estimate the impact. Seems good to start with the simplest rules, if we can get away with it, since we can always allow more later once we know how specialization pans out. |
This is still allowed with either rule, right? impl Foo<i32> {
fn abc(&self) { }
}
impl Foo<u32> {
fn abc(&self) { }
} |
@bluss No, that's only allowed by the second rule. The first rule essentially says that for any data type, a given method name can appear in at most one of its inherent |
@bluss If you know offhand of crates where this more conservative rule would cause significant breakage, pointers would be appreciated; that could save the time of making a branch and doing a crater run. |
The interpretation of "same function" I found most reasonable.. I realized after asking the question, Anyway, |
@bluss Sorry, to try to be crystal clear: when @nikomatsakis says "same fn" he means "function with the same name". |
@nikomatsakis The more conservative rule prevents bootstrapping, due to stable impls of impl Box<Any> {
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<Any>> { ... }
}
impl Box<Any + Send> {
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<Any + Send>> { ... }
} These impls are currently needed due to the lack of upcasting in dispatch. We could see how much breakage comes out of dropping the second impl... |
@aturon d'oh :) |
So, one issue with the less conservative rule: what requirements, if any, do we impose about the relevant signatures? For the The specialization RFC laid out thoughts on how to "infer" implicit traits based on inherent impls -- I believe the only requirement is matching arity. |
@aturon ah yes, I remember this algorithm now -- so do you believe that the only requirement we would have to enforce for future compat is to insist that the self types are non-overlapping and arity is the same? |
I was just looking at some compiler code that relies on this pattern of inherent impls in a way that is not really intended to act as specialization would act. For example: impl Binder<TraitPredicate> { ... }
impl Binder<LifetimePredicate> { ... } These two impls are really disjoint sets of methods with nothing in common. As it happens, I doubt that they have overlapping names, but if they did, there isn't necessarily a reason to expect that those methods would have the same arity (or purpose). In other words, they would be puns, not the same method specialized to different types. I'm not sure what to make of this. Just pointing it out. |
OK, @nikomatsakis and I discussed this some on IRC, and the consensus between us is that we do not need to impose any constraints on signatures. That is, the rule today should be: you can write whatever you like in inherent impl blocks, but if two blocks overlap on their That rules out things like: struct Foo;
impl Foo {
fn id() {}
}
impl Foo {
fn id() {}
}
struct Bar<T>(T);
impl<T> Bar<T> {
fn bar(&self) {}
}
impl Bar<i32> {
fn bar(&self) {}
} but allows things like: impl Box<Any> {
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<Any>> { ... }
}
impl Box<Any + Send> {
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<Any + Send>> { ... }
}
impl Foo<i32> {
fn abc(&self) { }
}
impl Foo<u32> {
fn abc(&self, flag: bool) { }
} I believe that if we want to allow specialization of inherent impls in the future, we can make it work with this approach. It'd be mildly more subtle than a version that requires matching arities everywhere, but that's a pretty arbitrary constraint anyway. |
Oh, and the point is that this approach also has minimal breakage, and the things it breaks (cases of overlap) really shouldn't be happening today. |
impl blocks. For example, the following is now correctly illegal: ```rust struct Foo; impl Foo { fn id() {} } impl Foo { fn id() {} } ``` "Overlapping" here is determined the same way it is for traits (and in fact shares the same code path): roughly, there must be some way of substituting any generic types to unify the impls, such that none of the `where` clauses are provably unsatisfiable under such a unification. Closes rust-lang#22889
Forbid items with the same name from appearing in overlapping inherent impl blocks For example, the following is now correctly illegal: ```rust struct Foo; impl Foo { fn id() {} } impl Foo { fn id() {} } ``` "Overlapping" here is determined the same way it is for traits (and in fact shares the same code path): roughly, there must be some way of substituting any generic types to unify the impls, such that none of the `where` clauses are provably unsatisfiable under such a unification. Along the way, this PR also introduces an `ImplHeader` abstraction (the first commit) that makes it easier to work with impls abstractly (without caring whether they are trait or inherent impl blocks); see the first commit. Closes #22889 r? @nikomatsakis
This code I used to compile fine with a version of Rust that was about 9 days old. All the comments in the code have been there before, now the code fails to compile at the lines
assert_eq!(Foo::id(), "FOO");
andassert_eq!(Bar::id(), "BAR");
:When compiling this now, I get this error, which seems wrong as
Bar
affectsFoo
:Even if this is now desired, I think the compiler message could be improved - it looks similar for the two candidates, and if it wasn't for the code shown below it, I would look like it means the same thing.
Meta
The text was updated successfully, but these errors were encountered: