Skip to content

Commit f8caa27

Browse files
committed
Amend rust-lang#1268 with a more feasible proposal post-specialization
1 parent bf9fca6 commit f8caa27

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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

Comments
 (0)