|
| 1 | +- Feature Name: Specialization on impls with no items |
| 2 | +- Start Date: 2016-04-30 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Assume that any trait impl which provides no items specializes any other impl |
| 10 | +which could apply to the same type, regardless of overlap. |
| 11 | + |
| 12 | +# Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +This RFC is intended to replace [RFC #1268][1268] |
| 16 | +entirely. Since its acceptance, little progress has been made on that RFC due to |
| 17 | +implementation concerns. With the advent of specialization, there are potential |
| 18 | +alternatives which are potentially easier to implement, and also addresses the |
| 19 | +drawbacks brought by that RFC. |
| 20 | + |
| 21 | +The motivations for the feature in general are the same as the original RFC, |
| 22 | +improving the ergonomics of implementing marker traits. |
| 23 | + |
| 24 | +Some examples include: |
| 25 | + |
| 26 | +- the coercible trait design presents at [RFC #91][91]; |
| 27 | +- the `ExnSafe` trait proposed in [RFC #1236][1236]. |
| 28 | + |
| 29 | +# Detailed design |
| 30 | +[design]: #detailed-design |
| 31 | + |
| 32 | +Much of the concern around [RFC #1268][1268] was related to difficulty of |
| 33 | +implementation. From the "Unresolved questions" section of the original RFC: |
| 34 | + |
| 35 | +> Today, we prefer to break down an obligation like |
| 36 | +> `Foo: MarkerTrait` into component obligations (e.g., `Foo: Send`). Due to |
| 37 | +> coherence, there is always one best way to do this. That is, there is a best |
| 38 | +> impl to choose. But under this proposal, there would not be. |
| 39 | +
|
| 40 | +> similar concerns arise with the proposals around specialization, so it may be |
| 41 | +> that progress on that front will answer the questions raised here |
| 42 | +
|
| 43 | +Specialization appears to have made some progress on this front. However, |
| 44 | +specialization is primarily focused on details of the trait impl, not on the |
| 45 | +trait itself. For that reason, it is proposed that we amend [RFC #1268][1268] to |
| 46 | +a definition which is more compatible with specialization. |
| 47 | + |
| 48 | +**Any trait impl which provides no items is assumed to specialize any other |
| 49 | +trait impl which could apply to the same type, regardless of overlap**. At the |
| 50 | +time of writing, the definition of a trait item is an associated const, type, or |
| 51 | +method. The exact meaning of an item may change in the future as language |
| 52 | +features are added, but the intention is that an impl providing no items means |
| 53 | +that the body of the trait impl is empty. |
| 54 | + |
| 55 | +[RFC #1210][1210] states the following constraint around allowing overlap: |
| 56 | + |
| 57 | +> There has to be a way to decide which of two overlapping impls to actually use |
| 58 | +> for a given set of input types. |
| 59 | +
|
| 60 | +This continues to hold with the proposed change, as an impl with no body is |
| 61 | +interchangeable with the less specific impl. This can be demonstrated with |
| 62 | +specialization as it exists today: |
| 63 | + |
| 64 | +```rust |
| 65 | +trait SayHello { |
| 66 | + fn hi(); |
| 67 | +} |
| 68 | + |
| 69 | +trait Foo {} |
| 70 | +trait Bar {} |
| 71 | + |
| 72 | +impl<T: Foo> SayHello for T { |
| 73 | + default fn hi() { |
| 74 | + println!("Hello there"); |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +impl<T: Foo + Bar> SayHello for T {} |
| 79 | +``` |
| 80 | + |
| 81 | +This change effectively has the same meaning as the original RFC, as an empty |
| 82 | +trait body would be rejected for a trait that requires adding items. However, |
| 83 | +there is an important difference in this meaning compared to the original. |
| 84 | + |
| 85 | +Overlap would become allowed between impls on traits with items, as long as |
| 86 | +neither overlapping trait provides any items. Expanding on the previous example, |
| 87 | +the following would hold: |
| 88 | + |
| 89 | +```rust |
| 90 | +trait SayHello { |
| 91 | + fn hi(); |
| 92 | +} |
| 93 | + |
| 94 | +trait Foo {} |
| 95 | +trait Bar {} |
| 96 | +trait Baz {} |
| 97 | + |
| 98 | +impl<T: Foo> SayHello for T { |
| 99 | + default fn hi() { |
| 100 | + println!("Hello there"); |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +impl<T: Foo + Bar> SayHello for T {} |
| 105 | +impl<T: Foo + Baz> SayHello for T {} |
| 106 | +``` |
| 107 | + |
| 108 | +This means that the only drawback from [RFC #1268](1268) is removed. Adding a |
| 109 | +defaulted item to a marker trait is no longer a breaking change. Adding items |
| 110 | +without a default is already considered to be a breaking change today. |
| 111 | + |
| 112 | +Other than overlap, the rules for what impls are accepted remain the same, |
| 113 | +however. Any impl which is not a `default impl` must provide an implementation |
| 114 | +for all required items unless they are provided by a less specific impl. That |
| 115 | +means that the following code would not compile: |
| 116 | + |
| 117 | +```rust |
| 118 | +trait SayHello { |
| 119 | + fn hi(); |
| 120 | +} |
| 121 | + |
| 122 | +trait Foo {} |
| 123 | +trait Bar {} |
| 124 | +trait Baz {} |
| 125 | + |
| 126 | +impl<T: Foo> SayHello for T { |
| 127 | + default fn hi() { |
| 128 | + println!("Hello there"); |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +impl<T: Bar> SayHello for T {} |
| 133 | +``` |
| 134 | + |
| 135 | +For any type `T` which implements `Bar`, but not `Foo`, the second impl is |
| 136 | +incomplete, and hence would be rejected. However, if the item has a default at |
| 137 | +the trait level, the code would compile: |
| 138 | + |
| 139 | +```rust |
| 140 | +trait SayHello { |
| 141 | + fn hi() { |
| 142 | + println!("Hi, there!"); |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +trait Foo {} |
| 147 | +trait Bar {} |
| 148 | +trait Baz {} |
| 149 | + |
| 150 | +impl<T: Foo> SayHello for T { |
| 151 | + default fn hi() { |
| 152 | + println!("Hello there"); |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +impl<T: Bar> SayHello for T {} |
| 157 | +``` |
| 158 | + |
| 159 | +Since `T: Bar` provides no items, it is assumed to specialize `T: Foo` if |
| 160 | +permitted for the given type when checking for coherence. Typeck would be |
| 161 | +satisfied for any type that fulfills `T: Foo`, `T: Bar`, or `T: Foo + Bar`. |
| 162 | +During monomorphisation in codegen, if a type satisfies either `Foo` or |
| 163 | +`Foo + Bar`, the code will print "Hello there". If a type satisfies `Bar` but |
| 164 | +not `Foo + Bar`, it will print "Hi, there!". |
| 165 | + |
| 166 | +The overall semantics are moderately complex, but become quite simple when you |
| 167 | +think of them in the context of the original intention. When a trait does not |
| 168 | +require any items, overlap is allowed. |
| 169 | + |
| 170 | +Due to the semantics of specialization, there is one additional wrinkle, which |
| 171 | +is that any `default impl` with no body would be accepted. This is quirky, but |
| 172 | +appears to be harmless. In practice there is no reason to write an empty |
| 173 | +`default impl` for anything. The effects of providing a `default impl` for a |
| 174 | +trait with no items is intentionally left unspecified (and in fact is unclear |
| 175 | +from the definition of specialization without this RFC). |
| 176 | + |
| 177 | +# Drawbacks |
| 178 | +[drawbacks]: #drawbacks |
| 179 | + |
| 180 | +While this proposal is likely easier to implement, and more flexible than |
| 181 | +[RFC #1268][1268], it is more complex and has edge cases that did not exist in |
| 182 | +the original. |
| 183 | + |
| 184 | +# Alternatives |
| 185 | +[alternatives]: #alternatives |
| 186 | + |
| 187 | +Continuing with [RFC #1268](1268) as written. |
| 188 | + |
| 189 | +# Unresolved questions |
| 190 | +[unresolved]: #unresolved-questions |
| 191 | + |
| 192 | +Is this actually easier to implement than the original RFC? This RFC was written |
| 193 | +as a result of attempting to implement [#1268](1268), and this proved to be much |
| 194 | +more straightforward. However, this needs to be verified by someone more well |
| 195 | +versed in the compiler's internals. |
| 196 | + |
| 197 | +[1210]: https://github.com/rust-lang/rfcs/pull/1210 |
| 198 | +[1236]: https://github.com/rust-lang/rfcs/pull/1236 |
| 199 | +[1268]: https://github.com/rust-lang/rfcs/pull/1268 |
| 200 | +[91]: https://github.com/rust-lang/rfcs/pull/91 |
0 commit comments