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 upForward compatibility hazard from coherence rules #23086
Comments
This comment has been minimized.
This comment has been minimized.
|
triage: P-backcompat-lang (1.0 beta) |
rust-highfive
added
the
P-backcompat-lang
label
Mar 5, 2015
rust-highfive
added this to the 1.0 beta milestone
Mar 5, 2015
This comment has been minimized.
This comment has been minimized.
|
This still won't solve all of these cases. An |
This comment has been minimized.
This comment has been minimized.
|
So I've been putting some thought into this and I just want to write out the current state of my thinking. As @arielb1 pointed out, the problems here run a bit deeper than I originally considered. Basically, in a coherence like system, almost any impl potentially imposes a kind of implicit negative constraint on the ancestor crates. Let me elaborate some of the scenarios. The problems are related to the orphan rules. In particular, the orphan rules require that a local type appear somewhere in the impl, but they don't require that local types "cover" remote types (so, e.g., Hazards arising inherently from coherenceBasically no matter what, there are always forward evolution hazards, particularly if a crate attempts to add blanket impls for an existing trait. Imagine we have a crate A that defines impl A::Trait for B::Type { } // in crate BThat impl prevents A from adding an impl like: impl<T> A::Trait for T where .. { } // in crate AThe only exception is when the new impl in A includes some where clause referencing a trait that is newly added in A and where we can be sure that downstream types do not yet implement this trait. Similarly, because of how the orphan rules are structured, crate B might have impls like: impl A::Trait for Box<B::Type> { } // in crate B
impl A::Trait for &B::Type { } // in crate BNaturally these would prevent crate A from adding blanket impls like:
(The same proviso about where clauses referencing new traits applies.) Note that "blanket" impls do not have to be involving smart pointers. For example, a relevant example is that libstd might want to implement impl<T:Copy> Copy for Range<T> { }Unfortunately, that could conflict if some downstream user had manually implemented impl Copy for Range<MyType> { }So basically the only thing that a crate can safely add is an impl with no generic type parameters in the local type: impl Copy for SomeType { /* note: no generic type parameters at all! */ }Mitigation strategies for the "inherent" conflicts of coherenceOne interesting aspect of these conflicts is that they are always "intra-trait". What I mean is that you wind up with a coherence violation because the ancestor and children creates both provide an impl of the same trait. This implies that one way to address this might be some sort of specialization-based design. There are lots of design questions to address here, but the basic idea would be that the ancestor trait can add a "low priority" blanket impl that subcrates can override. This could be added even after other crates may have already created more specialized impls without creating a disturbance. In the particular case of Hazards arising more specifically from negative reasoningThese hazards can arise somewhat indirectly as well, due to the negative reasoning that coherence uses. This was the issue I originally started talking about. For example, I might implement a trait like: trait Foo { .. }
impl<T:Copy> Foo for T { .. }
impl<T:Copy> Foo for Range<T> { .. }In this case, Mitigation strategies for negative boundsInterestingly, specialization doesn't help here. Whereas before the problem was that we created overlapping impls within a trait, in this case the problem is that growing a trait at all induces overlapping impls for another trait. Specialization can help us resole the first problem -- which impl to use within the first trait -- but it doesn't help for the second case, because libstd cannot change the set of impls for the trait To be clear, specialization might help if we had some rules such that the impls on Limiting negative reasoningMy initial proposal was to imply a sort of orphan-rule-like restriction on negative reasoning. The intuition is that you can apply negative reasoning to types in your own crate, but not upstream crates, because those upstream crates might grow. But the devil is in the details here. We presumably want to require that we can only rely on In particular, if use the definition from the orphan rules, then something like If we use a more stringent definition, say that all remote (or structural?) types must be covered by local types, then we definitely rule out patterns that are in use. The problem I encounter right now in libstd derives from the impl<E> FromError<E> for E
impl<'a, E: Error + 'a> FromError<E> for Box<Error + 'a>Here the problem is that the type |
This comment has been minimized.
This comment has been minimized.
Note in particular that things like |
This comment has been minimized.
This comment has been minimized.
|
(Also, note that |
This comment has been minimized.
This comment has been minimized.
|
Also, I wrote:
Is this true? I think it's true but perhaps this is a failure of imagination on my part? |
This comment has been minimized.
This comment has been minimized.
|
I investigated a bit more -- adding special rules around impl<'a, E: Error + 'a> FromError<E> for Box<Error + 'a> { }which conflicts here: impl<E> FromError<E> for Ein particular, we can't rely that |
This comment has been minimized.
This comment has been minimized.
|
More investigation. I added a flag I can use to disable the check on particular impls so that I could keep going and uncover all potentially problematic combinations. Here is another example conflict, this time in
I believe this is considered a conflict because It could be resolved by considering UPDATE: To clarify, I wrote |
This comment has been minimized.
This comment has been minimized.
|
Here is the branch that implements the experimental revised version: https://github.com/nikomatsakis/rust/commits/issue-23086-coherence-forward-compat the |
nikomatsakis
referenced this issue
Mar 19, 2015
Closed
overlap check fails to prevent overlap #23516
nikomatsakis
referenced this issue
Mar 25, 2015
Closed
Bounds seem to be ignored in negative impls #23072
This comment has been minimized.
This comment has been minimized.
|
(this can be punted from beta, but we hope a last minute design/fix that niko is working on will land in time for beta) |
nikomatsakis
self-assigned this
Mar 26, 2015
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 30, 2015
nikomatsakis
referenced this issue
Mar 30, 2015
Merged
Update orphan and overlap rules for RFC 1023 #23867
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Apr 1, 2015
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Apr 1, 2015
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Apr 1, 2015
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Apr 1, 2015
alexcrichton
referenced this issue
Apr 1, 2015
Closed
Tracking issue for Rebalancing Coherence (RFC 1023) #23918
alexcrichton
added
the
B-RFC-approved
label
Apr 1, 2015
This comment has been minimized.
This comment has been minimized.
|
Turning this into the tracking issue for RFC 1023 |
nikomatsakis commentedMar 5, 2015
The current coherence rules are too smart. I think we should consider adding some limits to rein it in. In particular, imagine that crate A defines:
And crate B then defines:
This is currently accepted today, I believe. However, I think it should not be, because it means that now crate A cannot add an impl of
CloneforFoofor fear of breaking crate B.My proposed fix is to modify the coherence check so that you can't assume that a type from another crate won't implement a trait from another crate.
cc @aturon