From 5ba01a5cc2911f3bda07ad61bec4473bdc6684c5 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 27 Mar 2015 18:01:25 -0400 Subject: [PATCH] Initial draft. --- text/0000-rebalancing-coherence.md | 293 +++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 text/0000-rebalancing-coherence.md diff --git a/text/0000-rebalancing-coherence.md b/text/0000-rebalancing-coherence.md new file mode 100644 index 00000000000..9f6c6b09553 --- /dev/null +++ b/text/0000-rebalancing-coherence.md @@ -0,0 +1,293 @@ +- Feature Name: fundamental_attribute +- Start Date: 2015-03-27 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +## Summary + +This RFC proposes two rule changes: + +1. Modify the orphan rules so that impls of remote traits require a + local type that is either a struct/enum/trait defined in the + current crate `LT = LocalTypeConstructor<...>` or a reference to a + local type `LT = ... | < | &mut LT`. +2. Restrict negative reasoning so it too obeys the orphan rules. +3. Introduce an unstable `#[fundamental]` attribute that can be used + to extend the above rules in select cases (details below). + +## Motivation + +The current orphan rules are oriented around allowing as many remote +traits as possible. As so often happens, giving power to one party (in +this case, downstream crates) turns out to be taking power away from +another (in this case, upstream crates). The problem is that due to +coherence, the ability to define impls is a zero-sum game: every impl +that is legal to add in a child crate is also an impl that a parent +crate cannot add without fear of breaking downstream crates. A +detailed look at these problems is +[presented here](https://gist.github.com/nikomatsakis/bbe6821b9e79dd3eb477); +this RFC doesn't go over the problems in detail, but will reproduce +some of the examples found in that document. + +This RFC proposes a shift that attempts to strike a balance between +the needs of downstream and upstream crates. In particular, we wish to +preserve the ability of upstream crates to add impls to traits that +they define, while still allowing downstream creates to define the +sorts of impls they need. + +While exploring the problem, we found that in practice remote impls +almost always are tied to a local type or a reference to a local +type. For example, here are some impls from the definition of `Vec`: + +```rust +// tied to Vec +impl Send for Vec + where T: Send + +// tied to &Vec +impl<'a,T> IntoIterator for &'a Vec +``` + +On this basis, we propose that we limit remote impls to require that +they include a type either defined in the current crate or a reference +to a type defined in the current crate. This is more restrictive than +the current definition, which merely requires a local type appear +*somewhere*. So, for example, under this definition `MyType` and +`&MyType` would be considered local, but `Box`, +`Option`, and `(MyType, i32)` would not. + +Furthermore, we limit the use of *negative reasoning* to obey the +orphan rules. That is, just as a crate cannot define an impl `Type: +Trait` unless `Type` or `Trait` is local, it cannot rely that `Type: +!Trait` holds unless `Type` or `Trait` is local. + +Together, these two changes cause very little code breakage while +retaining a lot of freedom to add impls in a backwards compatible +fashion. However, they are not quite sufficient to compile all the +most popular cargo crates (though they almost succeed). Therefore, we +propose an simple, unstable attribute `#[fundamental]` (described +below) that can be used to extend the system to accommodate some +additional patterns and types. This attribute is unstable because it +is not clear whether it will prove to be adequate or need to be +generalized; this part of the design can be considered somewhat +incomplete, and we expect to finalize it based on what we observe +after the 1.0 release. + +### Practical effect + +#### Effect on parent crates + +When you first define a trait, you must also decide whether that trait +should have (a) a blanket impls for all `T` and (b) any blanket impls +over references. These blanket impls cannot be added later without a +major vesion bump, for fear of breaking downstream clients. + +Here are some examples of the kinds of blanket impls that must be added +right away: + +```rust +impl Bar for T { } +impl<'a,T:Bar> Bar for &'a T { } +``` + +#### Effect on child crates + +Under the base rules, child crates are limited to impls that use local +types or references to local types. They are also prevented from +relying on the fact that `Type: !Trait` unless either `Type` or +`Trait` is local. This turns out to be have very little impact. + +In compiling the libstd facade and librustc, exactly two impls were +found to be illegal, both of which followed the same pattern: + +```rust +struct LinkedListEntry<'a> { + data: i32, + next: Option<&'a LinkedListEntry> +} + +impl<'a> Iterator for Option<&'a LinkedListEntry> { + type Item = i32; + + fn next(&mut self) -> Option { + if let Some(ptr) = *self { + *self = Some(ptr.next); + Some(ptr.data) + } else { + None + } + } +} +``` + +The problem here is that `Option<&LinkedListEntry>` is no longer +considered a local type. A similar restriction would be that one +cannot define an impl over `Box`; but this was not +observed in practice. + +Both of these restrictions can be overcome by using a new type. For +example, the code above could be changed so that instead of writing +the impl for `Option<&LinkedListEntry>`, we define a type `LinkedList` +that wraps the option and implement on that: + +```rust +struct LinkedListEntry<'a> { + data: i32, + next: LinkedList<'a> +} + +struct LinkedList<'a> { + data: Option<&'a LinkedListEntry> +} + +impl<'a> Iterator for LinkedList<'a> { + type Item = i32; + + fn next(&mut self) -> Option { + if let Some(ptr) = self.data { + *self = Some(ptr.next); + Some(ptr.data) + } else { + None + } + } +} +``` + +#### Errors from cargo and the fundamental attribute + +We also applied our prototype to all the "Most Downloaded" cargo +crates as well as the `iron` crate. That exercise uncovered a few +patterns that the simple rules presented thus far can't handle. + +The first is that it is common to implement traits over boxed trait +objects. For example, the `error` crate defines an impl: + +- `impl FromError for Box` + +Here, `Error` is a local trait defined in `error`, but `FromError` is +the trait from `libstd`. This impl would be illegal because +`Box` is not considered local as `Box` is not local. + +The second is that it is common to use `FnMut` in blanket impls, +similar to how the `Pattern` trait in `libstd` works. The `regex` crate +in particular has the following impls: + +- `impl<'t> Replacer for &'t str` +- `impl Replacer for F where F: FnMut(&Captures) -> String` +- these are in conflict because this requires that `&str: !FnMut`, and + neither `&str` nor `FnMut` are local to `regex` + +Given that overloading over closures is likely to be a common request, +and that the `Fn` traits are well-known, core traits tied to the call +operator, it seems reasonable to say that implementing a `Fn` trait is +itself a breaking change. (This is not to suggest that there is +something *fundamental* about the `Fn` traits that distinguish them +from all other traits; just that if the goal is to have rules that +users can easily remember, saying that implememting a core operator +trait is a breaking change may be a reasonable rule, and it enables +useful patterns to boot -- patterns that are baked into the libstd +APIs.) + +To accommodate these cases (and future cases we will no doubt +encounter), this RFC proposes an unstable attribute +`#[fundamental]`. `#[fundamental]` can be applied to types and traits +with the following meaning: + +- A `#[fundamental]` type `Foo` is one where implementing a blanket + impl over `Foo` is a breaking change. As described, `&` and `&mut` are + fundamental. This attribute would be applied to `Box`, making `Box` + behave the same as `&` and `&mut` with respect to coherence. +- A `#[fundamental]` trait `Foo` is one where adding an impl of `Foo` + for an existing type is a breaking change. For now, the `Fn` traits + and `Sized` would be marked fundamental, though we may want to + extend this set to all operators or some other + more-easily-remembered set. + +The `#[fundamental]` attribute is intended to be a kind of "minimal +commitment" that still permits the most important impl patterns we see +in the wild. Because it is unstable, it can only be used within libstd +for now. We are eventually committed to finding some way to +accommodate the patterns above -- which could be as simple as +stabilizing `#[fundamental]` (or, indeed, reverting this RFC +altogether). It could also be a more general mechanism that lets users +specify more precisely what kind of impls are reserved for future +expansion and which are not. + +## Detailed Design + +### Proposed orphan rules + +Given an impl `impl Trait for T0`, either `Trait` +must be local to the current crate, or: + +1. At least one type must meet the `LT` pattern defined above. Let + `Ti` be the first such type. +2. No type parameters `P1...Pn` may appear in the type parameters that + precede `Ti` (that is, `Tj` where `j < i`). + +### Type locality and negative reasoning + +Currently the overlap check employs negative reasoning to segregate +blanket impls from other impls. For example, the following pair of +impls would be legal only if `MyType: !Copy` for all `U` (the +notation `Type: !Trait` is borrowed from [RFC 586][586]): + +```rust +impl Clone for T {..} +impl Clone for MyType {..} +``` + +[586]: https://github.com/rust-lang/rfcs/pull/586 + +This proposal places limits on negative reasoning based on the orphan +rules. Specifically, we cannot conclude that a proposition like `T0: +!Trait` holds unless `T0: Trait` meets the orphan +rules as defined in the previous section. + +In practice this means that, by default, you can only assume negative +things about traits and types defined in your current crate, since +those are under your direct control. This permits parent crates to add +any impls except for blanket impls over `T`, `&T`, or `&mut T`, as +discussed before. + +### Effect on ABI compatibility and semver + +We have not yet proposed a comprehensive semver RFC (it's +coming). However, this RFC has some effect on what that RFC would say. +As discussed above, it is a breaking change for to add a blanket impl +for a `#[fundamental]` type. It is also a breaking change to add an +impl of a `#[fundamental]` trait to an existing type. + +# Drawbacks + +The primary drawback is that downstream crates cannot write an impl +over types other than references, such as `Option`. This +can be overcome by defining wrapper structs (new types), but that can +be annoying. + +# Alternatives + +- **Status quo.** In the status quo, the balance of power is heavily + tilted towards child crates. Parent crates basically cannot add any + impl for an existing trait to an existing type without potentially + breaking child crates. + +- **Take a hard line.** We could forego the `#[fundamental]` attribute, but + it would force people to forego `Box` impls as well as the + useful closure-overloading pattern. This seems + unfortunate. Moreover, it seems likely we will encounter further + examples of "reasonable cases" that `#[fundamental]` can easily + accommodate. + +- **Specializations, negative impls, and contracts.** The gist + referenced earlier includes [a section][c] covering various + alternatives that I explored which came up short. These include + specialization, explicit negative impls, and explicit contracts + between the trait definer and the trait consumer. + +# Unresolved questions + +None. + +[c]: https://gist.github.com/nikomatsakis/bbe6821b9e79dd3eb477#file-c-md