Skip to content

Commit e038eca

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

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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+
125+
impl<T: Foo> SayHello for T {
126+
default fn hi() {
127+
println!("Hello there");
128+
}
129+
}
130+
131+
impl<T: Bar> SayHello for T {}
132+
```
133+
134+
For any type `T` which implements `Bar`, but not `Foo`, the second impl is
135+
incomplete, and hence would be rejected. However, if the item has a default at
136+
the trait level, the code would compile:
137+
138+
```rust
139+
trait SayHello {
140+
fn hi() {
141+
println!("Hi, there!");
142+
}
143+
}
144+
145+
trait Foo {}
146+
trait Bar {}
147+
148+
impl<T: Foo> SayHello for T {
149+
default fn hi() {
150+
println!("Hello there");
151+
}
152+
}
153+
154+
impl<T: Bar> SayHello for T {}
155+
```
156+
157+
Since `T: Bar` provides no items, it is assumed to specialize `T: Foo` if
158+
permitted for the given type when checking for coherence. Typeck would be
159+
satisfied for any type that fulfills `T: Foo`, `T: Bar`, or `T: Foo + Bar`.
160+
During monomorphisation in codegen, if a type satisfies either `Foo` or
161+
`Foo + Bar`, the code will print "Hello there". If a type satisfies `Bar` but
162+
not `Foo + Bar`, it will print "Hi, there!".
163+
164+
The overall semantics are moderately complex, but become quite simple when you
165+
think of them in the context of the original intention. When a trait does not
166+
require any items, overlap is allowed.
167+
168+
Due to the semantics of specialization, there is one additional wrinkle, which
169+
is that any `default impl` with no body would be accepted. This is quirky, but
170+
appears to be harmless. In practice there is no reason to write an empty
171+
`default impl` for anything. The effects of providing a `default impl` for a
172+
trait with no items is intentionally left unspecified (and in fact is unclear
173+
from the definition of specialization without this RFC).
174+
175+
# Drawbacks
176+
[drawbacks]: #drawbacks
177+
178+
While this proposal is likely easier to implement, and more flexible than
179+
[RFC #1268][1268], it is more complex and has edge cases that did not exist in
180+
the original.
181+
182+
# Alternatives
183+
[alternatives]: #alternatives
184+
185+
Continuing with [RFC #1268](1268) as written.
186+
187+
# Unresolved questions
188+
[unresolved]: #unresolved-questions
189+
190+
Is this actually easier to implement than the original RFC? This RFC was written
191+
as a result of attempting to implement [#1268](1268), and this proved to be much
192+
more straightforward. However, this needs to be verified by someone more well
193+
versed in the compiler's internals.
194+
195+
[1210]: https://github.com/rust-lang/rfcs/pull/1210
196+
[1236]: https://github.com/rust-lang/rfcs/pull/1236
197+
[1268]: https://github.com/rust-lang/rfcs/pull/1268
198+
[91]: https://github.com/rust-lang/rfcs/pull/91

0 commit comments

Comments
 (0)