Skip to content

Commit 37b657e

Browse files
authored
Merge pull request #2451 from sgrif/sg-re-re-balancing-coherence
Re-Rebalancing Coherence
2 parents a09297e + d533b8d commit 37b657e

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed

text/2451-re-rebalancing-coherence.md

+318
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
- Feature Name: `re_rebalancing_coherence`
2+
- Start Date: 2018-05-30
3+
- RFC PR: [rust-lang/rfcs#2451](https://github.com/rust-lang/rfcs/pull/2451)
4+
- Rust Issue: [rust-lang/rust#55437](https://github.com/rust-lang/rust/issues/55437)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
This RFC seeks to clarify some ambiguity from [RFC #1023], and expands it to
10+
allow type parameters to appear in the type for which the trait is being
11+
implemented, regardless of whether a local type appears before them. More
12+
concretely, it allows `impl<T> ForeignTrait<LocalType> for ForeignType<T>` to be
13+
written.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
For better or worse, we allow implementing foreign traits for foreign types. For
19+
example, `impl From<Foo> for Vec<i32>` is something any crate can write, even
20+
though `From` is a foreign trait, and `Vec` is a foreign type. However, under
21+
the current coherence rules, we do not allow `impl<T> From<Foo> for Vec<T>`.
22+
23+
There's no good reason for this restriction. Fundamentally, allowing `for
24+
Vec<ForeignType>` requires all the same restrictions as allowing `Vec<T>`.
25+
Disallowing type parameters to appear in the target type restricts how crates
26+
can be extended.
27+
28+
Consider an example from Diesel. Diesel constructs an AST which represents a SQL
29+
query, and then provides a trait to construct the final SQL. Because different
30+
databases have different syntax, this trait is generic over the backend being
31+
used. Diesel wants to support third party crates which add new AST nodes, as
32+
well as crates which add support for new backends. The current rules make it
33+
impossible to support both.
34+
35+
The Oracle database requires special syntax for inserting multiple records in a
36+
single query. However, the impl required for this is invalid today. `impl<'a, T,
37+
U> QueryFragment<Oracle> for BatchInsert<'a, T, U>`. There is no reason for this
38+
impl to be rejected. The only impl that Diesel could add which would conflict
39+
with it would look like `impl<'a, T> QueryFragment<T> for BatchInsert<'a, Type1,
40+
Type2>`. Adding such an impl is already considered a major breaking change by
41+
[RFC #1023], which we'll expand on below.
42+
43+
For some traits, this can be worked around by flipping the self type with the
44+
type parameter to the trait. Diesel has done that in the past (e.g.
45+
`T: NativeSqlType<DB>` became `DB: HasSqlType<T>`). However, that wouldn't work
46+
for this case. A crate which adds a new AST node would no longer be able to
47+
implement the required trait for all backends. For example, a crate which added
48+
the `LOWER` function from SQL (which is supported by all databases) would not be
49+
able to write `impl<T, DB> QueryFragment<Lower<T>> for DB`.
50+
51+
Unless we expand the orphan rules, use cases like this one will never be
52+
possible, and a crate like Diesel will never be able to be designed in a
53+
completely extensible fashion.
54+
55+
# Guide-level explanation
56+
[guide-level-explanation]: #guide-level-explanation
57+
58+
## Definitions
59+
60+
Local Trait: A trait which was defined in the current crate. Whether a trait is
61+
local or not has nothing to do with type parameters. Given `trait Foo<T, U>`,
62+
`Foo` is always local, regardless of the types used for `T` or `U`.
63+
64+
Local Type: A struct, enum, or union which was defined in the current crate.
65+
This is not affected by type parameters. `struct Foo` is considered local, but
66+
`Vec<Foo>` is not. `LocalType<ForeignType>` is local. Type aliases and trait
67+
aliases do not affect locality.
68+
69+
Covered Type: A type which appears as a parameter to another type. For example,
70+
`T` is uncovered, but the `T` in `Vec<T>` is covered. This is only relevant for
71+
type parameters.
72+
73+
Blanket Impl: Any implementation where a type appears uncovered. `impl<T> Foo
74+
for T`, `impl<T> Bar<T> for T`, `impl<T> Bar<Vec<T>> for T`, and `impl<T> Bar<T>
75+
for Vec<T>` are considered blanket impls. However, `impl<T> Bar<Vec<T>> for
76+
Vec<T>` is not a blanket impl, as all instances of `T` which appear in this impl
77+
are covered by `Vec`.
78+
79+
Fundamental Type: A type for which you cannot add a blanket impl backwards
80+
compatibly. This includes `&`, `&mut`, and `Box`. Any time a type `T` is
81+
considered local, `&T`, `&mut T`, and `Box<T>` are also considered local.
82+
Fundamental types cannot cover other types. Any time the term "covered type" is
83+
used, `&T`, `&mut T`, and `Box<T>` are not considered covered.
84+
85+
## What is coherence and why do we care?
86+
87+
Let's start with a quick refresher on coherence and the orphan rules. Coherence
88+
means that for any given trait and type, there is one specific implementation
89+
that applies. This is important for Rust to be easy to reason about. When you
90+
write `<Foo as Bar>::trait_method`, the compiler needs to know what actual
91+
implementation to use.
92+
93+
In languages without coherence, the compiler has to have some way to choose
94+
which implementation to use when multiple implementations could apply. Scala
95+
does this by having complex scope resolution rules for "implicit" parameters.
96+
Haskell (when a discouraged flag is enabled) does this by picking an impl
97+
arbitrarily.
98+
99+
Rust's solution is to enforce that there is only one impl to choose from at all.
100+
While the rules required to enforce this are quite complex, the result is easy
101+
to reason about, and is generally considered to be quite important for Rust.
102+
New features like specialization allow more than one impl to apply, but for any
103+
given type and trait, there will always be exactly one which is most specific,
104+
and deterministically be chosen.
105+
106+
An important piece of enforcing coherence is restricting "orphan impls". An impl
107+
is orphaned if it is implementing a trait you don't own for a type you don't
108+
own. Rust's rules around this balance two separate, but related goals:
109+
110+
- Ensuring that two crates can't write impls that would overlap (e.g. no crate
111+
other than `std` can write `impl From<usize> for Vec<i32>`. If they could,
112+
your program might stop compiling just by using two crates with an overlapping
113+
impl).
114+
- Restricting the impls that can be written so crates can add implementations
115+
for traits/types they do own without worrying about breaking downstream
116+
crates.
117+
118+
## Teaching users
119+
120+
This change isn't something that would end up in a guide, and is mostly
121+
communicated through error messages. The most common one seen is [E0210]. The
122+
text of that error will be changed to approximate the following:
123+
124+
[E0210]: https://doc.rust-lang.org/error-index.html#E0210
125+
126+
> Generally speaking, Rust only permits implementing a trait for a type if either
127+
> the trait or type were defined in your program. However, Rust allows a limited
128+
> number of impls that break this rule, if they follow certain rules. This error
129+
> indicates a violation of one of those rules.
130+
>
131+
> A trait is considered local when {definition given above}. A type is considered
132+
> local when {definition given above}.
133+
>
134+
> When implementing a foreign trait for a foreign type, the trait must have one or
135+
> more type parameters. A type local to your crate must appear before any use of
136+
> any type parameters. This means that `impl<T> ForeignTrait<LocalType<T>, T> for
137+
> ForeignType` is valid, but `impl<T> ForeignTrait<T, LocalType<T>> for
138+
> ForeignType` is not.
139+
>
140+
> The reason that Rust considers order at all is to ensure that your
141+
> implementation does not conflict with one from another crate. Without this rule,
142+
> you could write `impl<T> ForeignTrait<T, LocalType> for ForeignType`, and
143+
> another crate could write `impl<T> ForeignTrait<TheirType, T> for ForeignType`,
144+
> which would overlap. For that reason, we require that your local type come
145+
> before the type parameter, since the only alternative would be disallowing these
146+
> implementations at all.
147+
148+
Additionally, the case of `impl<T> ForeignTrait<LocalType> for T` should be
149+
special cased, and given its own error message, which approximates the
150+
following:
151+
152+
> This error indicates an attempt to implement a trait from another crate for a
153+
> type parameter.
154+
>
155+
> Rust requires that for any given trait and any given type, there is at most one
156+
> implmentation of that trait. An important piece of this is that we disallow
157+
> implementing a trait from another crate for a type parameter.
158+
>
159+
> Rust's orphan rule always permits an impl if either the trait or the type being
160+
> implemented are local to the current crate. Therefore, we can't allow `impl<T>
161+
> ForeignTrait<LocalTypeCrateA> for T`, because it might conflict with another crate
162+
> writing `impl<T> ForeignTrait<T> for LocalTypeCrateB`, which we will always
163+
> permit.
164+
165+
Finally, [RFC #1105] states that implementing any non-fundamental trait for an
166+
existing type is not a breaking change. This directly condradicts [RFC #1023],
167+
which is entirely based around "blanket impls" being breaking changes.
168+
Regardless of whether the changes proposed to the orphan rules in this proposal
169+
are accepted, a blanket impl being a breaking change *must* be true today. Given
170+
that the compiler currently accepts `impl From<Foo> for Vec<Foo>`, adding
171+
`impl<T> From<T> for Vec<T>` must be considered a major breaking change.
172+
173+
As such, [RFC #1105] is amended to remove the statement that implementing a
174+
non-fundamental trait is a minor breaking change, and states that adding any
175+
blanket impl for an existing trait is a major breaking change, using the
176+
definition of blanket impl given above.
177+
178+
# Reference-level explanation
179+
[reference-level-explanation]: #reference-level-explanation
180+
181+
## Concrete orphan rules
182+
183+
Assumes the same definitions [as above](#definitions).
184+
185+
Given `impl<P1..=Pn> Trait<T1..=Tn> for T0`, an impl is valid only if at
186+
least one of the following is true:
187+
188+
- `Trait` is a local trait
189+
- All of
190+
- At least one of the types `T0..=Tn` must be a local type. Let `Ti` be the
191+
first such type.
192+
- No uncovered type parameters `P1..=Pn` may appear in `T0..Ti` (excluding
193+
`Ti`)
194+
195+
The primary change from the rules defined in in [RFC #1023] is that we only
196+
restrict the appearance of *uncovered* type parameters. Once again, it is
197+
important to note that for the purposes of coherence, `#[fundamental]` types are
198+
special. `Box<T>` is not considered covered, and `Box<LocalType>` is considered
199+
local.
200+
201+
Under this proposal, the orphan rules continue to work generally as they did
202+
before, with one notable exception; We will permit `impl<T>
203+
ForeignTrait<LocalType> for ForeignType<T>`. This is completely valid under the
204+
forward compatibility rules set in [RFC #1023]. We can demonstrate that this is
205+
the case with the following:
206+
207+
- Any valid impl of `ForeignTrait` in a child crate must reference at least one
208+
type that is local to the child crate.
209+
- The only way a parent crate can reference the type of a child crate is with a
210+
type parameter.
211+
- For the impl in child crate to overlap with an impl in parent crate, the type
212+
parameter must be uncovered.
213+
- Adding any impl with an uncovered type parameter is considered a major
214+
breaking change.
215+
216+
We can also demonstrate that it is impossible for two sibling crates to write
217+
conflicting impls, with or without this proposal.
218+
219+
- Any valid impl of `ForeignTrait` in a child crate must reference at least one
220+
type that is local to the child crate.
221+
- The only way a local type of sibling crate A could overlap with a type used in
222+
an impl from sibling crate B is if sibling crate B used a type parameter
223+
- Any type parameter used by sibling crate B must be preceded by a local type
224+
- Sibling crate A could not possibly name a type from sibling crate B, thus that
225+
parameter can never overlap.
226+
227+
## Effects on parent crates
228+
229+
[RFC #1023] is amended to state that adding a new impl to an existing trait is
230+
considered a breaking change unless, given `impl<P1..=Pn> Trait<T1..=Tn> for
231+
T0`:
232+
233+
- At least one of the types `T0..=Tn` must be a local type, added in this
234+
revision. Let `Ti` be the first such type.
235+
- No uncovered type parameters `P1..=Pn` appear in `T0..Ti` (excluding `Ti`)
236+
237+
The more general way to put this rule is: "Adding an impl to an existing trait
238+
is a breaking change if it could possibly conflict with a legal impl in a
239+
downstream crate".
240+
241+
This clarification is true regardless of whether the changes in this proposal
242+
are accepted or not. Given that the compiler currently accepts `impl From<Foo> for
243+
Vec<Foo>`, adding the impl `impl<T> From<T> for Vec<T>` *must* be considered a
244+
major breaking change.
245+
246+
To be specific, the following adding any of the following impls would be
247+
considered a breaking change:
248+
249+
- `impl<T> OldTrait<T> for OldType`
250+
- `impl<T> OldTrait<AnyType> for T`
251+
- `impl<T> OldTrait<T> for ForeignType`
252+
253+
However, the following impls would not be considered a breaking change:
254+
255+
- `impl NewTrait<AnyType> for AnyType`
256+
- `impl<T> OldTrait<T> for NewType`
257+
- `impl<T> OldTrait<NewType, T> for OldType`
258+
259+
# Drawbacks
260+
[drawbacks]: #drawbacks
261+
262+
The current rules around coherence are complex and hard to explain. While this
263+
proposal feels like a natural extension of the current rules, and something many
264+
expect to work, it does make them slightly more complex.
265+
266+
The orphan rules are often taught as "for an impl `impl Trait for Type`, either
267+
Trait or Type must be local to your crate". While this has never been actually
268+
true, it's a reasonable hand-wavy explanation, and this gets us even further
269+
from it. Even though `impl From<Foo> for Vec<()>` has always been accepted,
270+
`impl<T> From<Foo> for Vec<T>` *feels* even less local. While `Vec<()>` only
271+
applies to `std`, `Vec<T>` now applies to types from `std` and any other crate.
272+
273+
# Rationale and alternatives
274+
[alternatives]: #alternatives
275+
276+
- Rework coherence even more deeply. The rules around the orphan rule are
277+
complex and hard to explain. Even `--explain E0210` doesn't actually try to
278+
give the rationale behind them, and just states the fairly arcane formula from
279+
the original RFC. While this proposal is a natural extension of the current
280+
rules, and something that many expect to "just work", it ultimately makes them
281+
even more complex.
282+
283+
In particular, this keeps the "ordering" rule. It still serves *a* purpose
284+
with this proposal, but much less of one. By keeping it, we are able to allow
285+
`impl<T> SomeTrait<LocalType, T> for ForeignType`, because no sibling crate
286+
can write an overlapping impl. However, this is not something that the
287+
majority of library authors are aware of, and requires API designers to order
288+
their type parameters based on how likely they are to be overidden by other
289+
crates.
290+
291+
We could instead provide a mechanism for traits to opt into a redesigned
292+
coherence system, and potentially default to that in a future edition.
293+
However, that would likely cause a lot of confusion in the community. This
294+
proposal is a strict addition to the set of impls which are allowed with the
295+
current rules, without an increase in risk or impls which are breaking
296+
changes. It seems like a reasonably conservative move, even if we eventually
297+
want to overhaul coherence.
298+
299+
- Get rid of the orphan rule entirely. A long standing pain point for crates
300+
like Diesel has been integration with other crates. Diesel doesn't want to
301+
care about chrono, and chrono doesn't want to care about Diesel. A database
302+
access library shouldn't dictate your choice of time libraries, vice versa.
303+
304+
However, due to the way Rust works today, one of them has to. Nobody can
305+
create a `diesel-chrono` crate due to the orphan rule. Maybe if we just
306+
allowed crates to have incompatible impls, and set a standard of "don't write
307+
orphan impls unless that's the entire point of your crate", it wouldn't
308+
actually be that bad.
309+
310+
# Unresolved questions
311+
[unresolved]: #unresolved-questions
312+
313+
- Are there additional implementations which are clearly acceptable under the
314+
current restrictions, which are disallowed with this extension? Should we
315+
allow them if so?
316+
317+
[RFC #1023]: https://github.com/rust-lang/rfcs/blob/master/text/1023-rebalancing-coherence.md
318+
[RFC #1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md

0 commit comments

Comments
 (0)