Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upDuplicate inherent static methods can be defined in separate `impl` blocks #22889
Comments
steveklabnik
added
the
A-traits
label
Mar 4, 2015
huonw
added
the
T-compiler
label
Jan 8, 2016
This comment has been minimized.
This comment has been minimized.
|
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 |
rust-highfive
added
the
I-nominated
label
Jan 8, 2016
huonw
changed the title
Traits and type-aliases: previously valid code now fails to compile
Duplicate inherent static methods can be defined in separate `impl` blocks
Jan 8, 2016
nikomatsakis
added
the
T-lang
label
Jan 14, 2016
This comment has been minimized.
This comment has been minimized.
|
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:
|
This comment has been minimized.
This comment has been minimized.
|
The latter is obviously more backwards compatible, so it'd be good to try and get some crater results. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
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. |
aturon
self-assigned this
Jan 14, 2016
nikomatsakis
unassigned
aturon
Jan 14, 2016
This comment has been minimized.
This comment has been minimized.
|
triage: P-high |
rust-highfive
added
P-high
and removed
I-nominated
labels
Jan 14, 2016
This comment has been minimized.
This comment has been minimized.
|
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 comment has been minimized.
This comment has been minimized.
|
This is still allowed with either rule, right? impl Foo<i32> {
fn abc(&self) { }
}
impl Foo<u32> {
fn abc(&self) { }
} |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
The interpretation of "same function" I found most reasonable.. I realized after asking the question, Anyway, |
This comment has been minimized.
This comment has been minimized.
|
@bluss Sorry, to try to be crystal clear: when @nikomatsakis says "same fn" he means "function with the same name". |
This comment has been minimized.
This comment has been minimized.
|
@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... |
This comment has been minimized.
This comment has been minimized.
|
@aturon d'oh :) |
This comment has been minimized.
This comment has been minimized.
|
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
self-assigned this
Jan 19, 2016
This comment has been minimized.
This comment has been minimized.
|
@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? |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
Byron commentedFeb 28, 2015
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
BaraffectsFoo: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