Skip to content

Commit 4c32c86

Browse files
committed
Re-Rebalancing Coherence
RFC #1023 introduced rules that exclude impls which should clearly be valid. It also used some ambiguous language around what is a breaking change, that ended up being completely ignored and contradicted by #1105. This RFC seeks to clarify what is or isn't a breaking change when it comes to implementing an existing trait, and conservatively expands the orphan rules to allow impls which do not violate coherence, and fit within the original goals of #1023.
1 parent 7a2ce51 commit 4c32c86

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed

text/0000-re-rebalancing-coherence.md

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
- Feature Name: `re_rebalancing_coherence`
2+
- Start Date: 2018-05-30
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
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 do not affect
67+
locality.
68+
69+
Covered Type: A type which appears as a parameter to another type. For example,
70+
`T` is uncovered, but `Vec<T>` is covered. This is only relevant for type
71+
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 types which appear in this impl are
77+
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 does this by picking one at random.
97+
98+
Rust's solution is to enforce that there is only one impl to choose from at all.
99+
While the rules required to enforce this are quite complex, the result is easy
100+
to reason about, and is generally considered to be quite important for Rust.
101+
New features like specialization allow more than one impl to apply, but for any
102+
given type and trait, there will always be exactly one which is most specific,
103+
and deterministically be chosen.
104+
105+
An important piece of enforcing coherence is restricting "orphan impls". An impl
106+
is orphaned if it is implementing a trait you don't own for a type you don't
107+
own. Rust's rules around this balance two separate, but related goals:
108+
109+
- Ensuring that two crates can't write impls that would overlap (e.g. no crate
110+
other than `std` can write `impl From<usize> for Vec<i32>`. If they could,
111+
your program might stop compiling just by using two crates with an overlapping
112+
impl).
113+
- Restricting the impls that can be written so crates can add implementations
114+
for traits/types they do own without worrying about breaking downstream
115+
crates.
116+
117+
## Teaching users
118+
119+
This change isn't something that would end up in a guide, and is mostly
120+
communicated through error messages. The most common one seen is [E0210]. The
121+
text of that error will be changed to approximate the following:
122+
123+
[E0210]: https://doc.rust-lang.org/error-index.html#E0210
124+
125+
> Generally speaking, Rust only permits implementing a trait for a type if either
126+
> the trait or type were defined in your program. However, Rust allows a limited
127+
> number of impls that break this rule, if they follow certain rules. This error
128+
> indicates a violation of one of those rules.
129+
>
130+
> A trait is considered local when {definition given above}. A type is considered
131+
> local when {definition given above}.
132+
>
133+
> When implementing a foreign trait for a foreign type, the trait must have one or
134+
> more type parameters. A type local to your crate must appear before any use of
135+
> any type parameters. This means that `impl<T> ForeignTrait<LocalType<T>, T> for
136+
> ForeignType` is valid, but `impl<T> ForeignTrait<T, LocalType<T>> for
137+
> ForeignType` is not.
138+
>
139+
> The reason that Rust considers order at all is to ensure that your
140+
> implementation does not conflict with one from another crate. Without this rule,
141+
> you could write `impl<T> ForeignTrait<T, LocalType> for ForeignType`, and
142+
> another crate could write `impl<T> ForeignTrait<TheirType, T> for ForeignType`,
143+
> which would overlap. For that reason, we require that your local type come
144+
> before the type parameter, since the only alternative would be disallowing these
145+
> implementations at all.
146+
147+
Additionally, the case of `impl<T> ForeignTrait<LocalType> for T` should be
148+
special cased, and given its own error message, which approximates the
149+
following:
150+
151+
> This error indicates an attempt to implement a trait from another crate for a
152+
> type parameter.
153+
>
154+
> Rust requires that for any given trait and any given type, there is at most one
155+
> implmentation of that trait. An important piece of this is that we disallow
156+
> implementing a trait from another crate for a type parameter.
157+
>
158+
> Rust's orphan rule always permits an impl if either the trait or the type being
159+
> implemented are local to the current crate. Therefore, we can't allow `impl<T>
160+
> ForeignTrait<LocalTypeCrateA> for T`, because it might conflict with another crate
161+
> writing `impl<T> ForeignTrait<T> for LocalTypeCrateB`, which we will always
162+
> permit.
163+
164+
Finally, [RFC #1105] states that implementing any non-fundamental trait for an
165+
existing type is not a breaking change. This directly condradicts [RFC #1023],
166+
which is entirely based around "blanket impls" being breaking changes.
167+
Regardless of whether the changes proposed to the orphan rules in this proposal
168+
are accepted, a blanket impl being a breaking change *must* be true today. Given
169+
that the compiler currently accepts `impl From<Foo> for Vec<Foo>`, adding
170+
`impl<T> From<T> for Vec<T>` must be considered a major breaking change.
171+
172+
As such, [RFC #1105] is amended to remove the statement that implementing a
173+
non-fundamental trait is a minor breaking change, and states that adding any
174+
blanket impl for an existing trait is a major breaking change, using the
175+
definition of blanket impl given above.
176+
177+
# Reference-level explanation
178+
[reference-level-explanation]: #reference-level-explanation
179+
180+
## Concrete orphan rules
181+
182+
Assumes the same definitions [as above](#definitions).
183+
184+
Given `impl<P1...Pn> Trait<T1...Tn> for Target`, an impl is valid only if at
185+
least one of the following is true:
186+
187+
- `Trait` is a local trait
188+
- `Target` is a local type
189+
- All of
190+
- `Target` is not an uncovered type parameter (is not any of `P1...Pn`)
191+
- At least one of the types `T1...Tn` must be a local type. Let `Ti` be the
192+
first such type.
193+
- No type parameters `P1...Pn` may appear *anywhere* in `T1...Tn` before `Ti`.
194+
195+
Note that this is not just written as the removal of `T0` from the rules defined
196+
in [RFC #1023]. We have the special case rule that `Target` must not be an
197+
uncovered type parameter, because we want to continue to reject `impl<T>
198+
ForeignTrait<LocalType> for T`. This must continue to be rejected to avoid
199+
sibling crates being able to conflict, as this impl would conflict with `impl<T>
200+
ForeignTrait<T> for LocalTypeInCrateB`.
201+
202+
Under this proposal, the orphan rules continue to work generally as they did
203+
before, with one notable exception; We will permit `impl<T>
204+
ForeignTrait<LocalType> for ForeignType<T>`. This is completley valid under the
205+
forward compatibility rules set in [RFC #1023]. We can demonstrate that this is
206+
the case with the following:
207+
208+
- Any valid impl of `ForeignTrait` in a child crate must reference at least one
209+
type that is local to the child crate.
210+
- The only way a parent crate can reference the type of a child crate is with a
211+
type parameter.
212+
- For the impl in child crate to overlap with an impl in parent crate, the type
213+
parameter must be uncovered.
214+
- Adding any impl with an uncovered type parameter is considered a major
215+
breaking change.
216+
217+
We can also demonstrate that it is impossible for two sibling crates to write
218+
conflicting impls, with or without this proposal.
219+
220+
- Any valid impl of `ForeignTrait` in a child crate must reference at least one
221+
type that is local to the child crate.
222+
- The only way a local type of sibling crate A could overlap with a type used in
223+
an impl from sibling crate B is if sibling crate B used a type parameter
224+
- Any type parameter used by sibling crate B must be preceded by a local type
225+
- Sibling crate A could not possibly name a type from sibling crate B, thus that
226+
parameter can never overlap.
227+
228+
## Effects on parent crates
229+
230+
[RFC #1023] is amended to state that adding a new impl to an existing trait is
231+
considered a breaking change if, given `impl<P1...Pn> Trait<T1...Tn> for T0`,
232+
any type `T0...Tn` is an uncovered type parameter (is any of `P1...Pn`).
233+
234+
This clarification is true regardless of whether the changes in this proposal
235+
are accepted or not. Given that the compiler currently accepts `impl From<Foo> for
236+
Vec<Foo>`, adding the impl `impl<T> From<T> for Vec<T>` *must* be considered a
237+
major breaking change. Note that the problem is `From<T>`, not `Vec<T>`. Adding
238+
`impl<T> From<i32> for Vec<T>` is not a breaking change.
239+
240+
# Drawbacks
241+
[drawbacks]: #drawbacks
242+
243+
The current rules around coherence are complex and hard to explain. While this
244+
proposal feels like a natural extension of the current rules, and something many
245+
expect to work, it does make them slightly more complex.
246+
247+
The orphan rules are often taught as "for an impl `impl Trait for Type`, either
248+
Trait or Type must be local to your crate". While this has never been actually
249+
true, it's a reasonable hand-wavy explanation, and this gets us even further
250+
from it. Even though `impl From<Foo> for Vec<()>` has always been accepted,
251+
`impl<T> From<Foo> for Vec<T>` *feels* even less local. While `Vec<()>` only
252+
applies to `std`, `Vec<T>` now applies to types from `std` and any other crate.
253+
254+
# Rationale and alternatives
255+
[alternatives]: #alternatives
256+
257+
- Rework coherence even more deeply. The rules around the orphan rule are
258+
complex and hard to explain. Even `--explain E0210` doesn't actually try to
259+
give the rationale behind them, and just states the fairly arcane formula from
260+
the original RFC. While this proposal is a natural extension of the current
261+
rules, and something that many expect to "just work", it ultimately makes them
262+
even more complex.
263+
264+
In particular, this keeps the "ordering" rule. It still serves *a* purpose
265+
with this proposal, but much less of one. By keeping it, we are able to allow
266+
`impl<T> SomeTrait<LocalType, T> for ForeignType`, because no sibling crate
267+
can write an overlapping impl. However, this is not something that the
268+
majority of library authors are aware of, and requires API designers to order
269+
their type parameters based on how likely they are to be overidden by other
270+
crates.
271+
272+
We could instead provide a mechanism for traits to opt into a redesigned
273+
coherence system, and potentially default to that in a future edition.
274+
However, that would likely cause a lot of confusion in the community. This
275+
proposal is a strict addition to the set of impls which are allowed with the
276+
current rules, without an increase in risk or impls which are breaking
277+
changes. It seems like a reasonably conservative move, even if we eventually
278+
want to overhaul coherence.
279+
280+
- Get rid of the orphan rule entirely. A long standing pain point for crates
281+
like Diesel has been integration with other crates. Diesel doesn't want to
282+
care about chrono, and chrono doesn't want to care about Diesel. A database
283+
access library shouldn't dictate your choice of time libraries, vice versa.
284+
285+
However, due to the way Rust works today, one of them has to. Nobody can
286+
create a `diesel-chrono` crate due to the orphan rule. Maybe if we just
287+
allowed crates to have incompatible impls, and set a standard of "don't write
288+
orphan impls unless that's the entire point of your crate", it wouldn't
289+
actually be that bad.
290+
291+
# Unresolved questions
292+
[unresolved]: #unresolved-questions
293+
294+
- Are there additional implementations which are clearly acceptable under the
295+
current restrictions, which are disallowed with this extension? Should we
296+
allow them if so?
297+
298+
[RFC #1023]: https://github.com/rust-lang/rfcs/blob/master/text/1023-rebalancing-coherence.md
299+
[RFC #1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md

0 commit comments

Comments
 (0)