-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Forward compatibility hazard from coherence rules #23086
Comments
triage: P-backcompat-lang (1.0 beta) |
This still won't solve all of these cases. An |
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 B That impl prevents A from adding an impl like: impl<T> A::Trait for T where .. { } // in crate A The 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 B Naturally 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 |
Note in particular that things like |
(Also, note that |
Also, I wrote:
Is this true? I think it's true but perhaps this is a failure of imagination on my part? |
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 E in particular, we can't rely that |
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 |
Here is the branch that implements the experimental revised version: https://github.com/nikomatsakis/rust/commits/issue-23086-coherence-forward-compat the |
(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) |
probing the specifics of `Fundamental`. Fixes rust-lang#23086. Fixes rust-lang#23516.
Turning this into the tracking issue for RFC 1023 |
…pnkfelix This PR implements rust-lang/rfcs#1023. In the process it fixes rust-lang#23086 and rust-lang#23516. A few impls in libcore had to be updated, but the impact is generally pretty minimal. Most of the fallout is in the tests that probed the limits of today's coherence. I tested and we were able to build the most popular crates along with iron (modulo errors around errors being sendable). Fixes rust-lang#23918.
This PR implements rust-lang/rfcs#1023. In the process it fixes rust-lang#23086 and rust-lang#23516. A few impls in libcore had to be updated, but the impact is generally pretty minimal. Most of the fallout is in the tests that probed the limits of today's coherence. I tested and we were able to build the most popular crates along with iron (modulo errors around errors being sendable). Fixes rust-lang#23918.
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
Clone
forFoo
for 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
The text was updated successfully, but these errors were encountered: