Skip to content

Commit 2d5218a

Browse files
authored
Merge pull request #2289 from Centril/rfc/associated-type-bounds
RFC: Associated type bounds of form `MyTrait<AssociatedType: Bounds>`
2 parents 0412428 + 2a685b5 commit 2d5218a

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

text/2289-associated-type-bounds.md

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
- Feature Name: `associated_type_bounds`
2+
- Start Date: 2018-01-13
3+
- RFC PR: [rust-lang/rfcs#2289](https://github.com/rust-lang/rfcs/pull/2289)
4+
- Rust Issue: [rust-lang/rust#52662](https://github.com/rust-lang/rust/issues/52662)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Introduce the bound form `MyTrait<AssociatedType: Bounds>`, permitted anywhere
10+
a bound of the form `MyTrait<AssociatedType = T>` would be allowed. The bound
11+
`T: Trait<AssociatedType: Bounds>` desugars to the bounds `T: Trait` and
12+
`<T as Trait>::AssociatedType: Bounds`.
13+
See the [reference][reference-level-explanation] and [rationale][alternatives]
14+
for exact details.
15+
16+
# Motivation
17+
[motivation]: #motivation
18+
19+
Currently, when specifying a bound using a trait that has an associated
20+
type, the developer can specify the precise type via the syntax
21+
`MyTrait<AssociatedType = T>`. With the introduction of the `impl Trait`
22+
syntax for static-dispatch existential types, this syntax also permits
23+
`MyTrait<AssociatedType = impl Bounds>`, as a shorthand for introducing a
24+
new type variable and specifying those bounds.
25+
26+
However, this introduces an unnecessary level of indirection that does not
27+
match the developer's intuition and mental model as well as it could. In
28+
particular, given the ability to write bounds on a type variable as `T: Bounds`,
29+
it makes sense to permit writing bounds on an associated type directly.
30+
This results in the simpler syntax `MyTrait<AssociatedType: Bounds>`.
31+
32+
# Guide-level explanation
33+
[guide-level-explanation]: #guide-level-explanation
34+
35+
Instead of specifying a concrete type for an associated type, we can
36+
specify a bound on the associated type, to ensure that it implements
37+
specific traits, as seen in the example below:
38+
39+
```rust
40+
fn print_all<T: Iterator<Item: Display>>(printables: T) {
41+
for p in printables {
42+
println!("{}", p);
43+
}
44+
}
45+
```
46+
47+
## In anonymous existential types
48+
49+
```rust
50+
fn printables() -> impl Iterator<Item: Display> {
51+
// ..
52+
}
53+
```
54+
55+
## Further examples
56+
57+
Instead of writing:
58+
59+
```rust
60+
impl<I> Clone for Peekable<I>
61+
where
62+
I: Clone + Iterator,
63+
<I as Iterator>::Item: Clone,
64+
{
65+
// ..
66+
}
67+
```
68+
69+
you may write:
70+
71+
```rust
72+
impl<I> Clone for Peekable<I>
73+
where
74+
I: Clone + Iterator<Item: Clone>
75+
{
76+
// ..
77+
}
78+
```
79+
80+
or replace the `where` clause entirely:
81+
82+
```rust
83+
impl<I: Clone + Iterator<Item: Clone>> Clone for Peekable<I> {
84+
// ..
85+
}
86+
```
87+
88+
# Reference-level explanation
89+
[reference-level-explanation]: #reference-level-explanation
90+
91+
The surface syntax `T: Trait<AssociatedType: Bounds>` should desugar to a pair
92+
of bounds: `T: Trait` and `<T as Trait>::AssociatedType: Bounds`.
93+
Rust currently allows both of those bounds anywhere a bound can currently appear;
94+
the new syntax does not introduce any new semantics.
95+
96+
Additionally, the surface syntax `impl Trait<AssociatedType: Bounds>` turns
97+
into a named type variable `T`, universal or existential depending on context,
98+
with the usual bound `T: Trait` along with the added bound
99+
`<T as Trait>::AssociatedType: Bounds`.
100+
101+
Meanwhile, the surface syntax `dyn Trait<AssociatedType: Bounds>` desugars into
102+
`dyn Trait<AssociatedType = T>` where `T` is a named type variable `T` with the
103+
bound `T: Bounds`.
104+
105+
## The desugaring for associated types
106+
107+
In the case of an associated type having a bound of the form:
108+
109+
```rust
110+
trait TraitA {
111+
type AssocA: TraitB<AssocB: TraitC>;
112+
}
113+
```
114+
115+
we desugar to an anonymous associated type for `AssocB`, which corresponds to:
116+
117+
```rust
118+
trait TraitA {
119+
type AssocA: TraitB<AssocB = Self::AssocA_0>;
120+
type AssocA_0: TraitC; // Associated type is Unnamed!
121+
}
122+
```
123+
124+
## Notes on the meaning of `impl Trait<Assoc: Bound>`
125+
126+
Note that in the context `-> impl Trait<Assoc: Bound>`, since the Trait is
127+
existentially quantified, the `Assoc` is as well. Semantically speaking,
128+
`fn printables..` is equivalent to:
129+
130+
```rust
131+
fn printables() -> impl Iterator<Item = impl Display> { .. }
132+
```
133+
134+
For `arg: impl Trait<Assoc: Bound>`, it is semantically equivalent to:
135+
`arg: impl Trait<Assoc = impl Bound>`.
136+
137+
## Meaning of `existential type Foo: Trait<Assoc: Bound>`
138+
139+
Given:
140+
141+
```
142+
existential type Foo: Trait<Assoc: Bound>;
143+
```
144+
145+
it can be seen as the same as:
146+
147+
```rust
148+
existential type Foo: Trait<Assoc = _0>;
149+
existential type _0: Bound;
150+
```
151+
152+
[RFC 2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-type-alias.md
153+
154+
This syntax is specified in [RFC 2071]. As in that RFC, this documentation
155+
uses the non-final syntax for existential type aliases.
156+
157+
# Drawbacks
158+
[drawbacks]: #drawbacks
159+
160+
Rust code can already express this using the desugared form. This proposal
161+
just introduces a simpler surface syntax that parallels other uses of bounds.
162+
As always, when introducing new syntactic forms, an increased burden is put on
163+
developers to know about and understand those forms, and this proposal is no
164+
different. However, we believe that the parallel to the use of bounds elsewhere
165+
makes this new syntax immediately recognizable and understandable.
166+
167+
# Rationale and alternatives
168+
[alternatives]: #rationale-and-alternatives
169+
170+
As with any new surface syntax, one alternative is simply not introducing
171+
the syntax at all. That would still leave developers with the
172+
`MyTrait<AssociatedType = impl Bounds>` form. However, allowing the more
173+
direct bounds syntax provides a better parallel to the use of bounds elsewhere.
174+
The introduced form in this RFC is comparatively both shorter and clearer.
175+
176+
### An alternative desugaring of bounds on associated types
177+
178+
[RFC 2089]: https://github.com/rust-lang/rfcs/blob/master/text/2089-implied-bounds.md
179+
180+
An alternative desugaring of the following definition:
181+
182+
```rust
183+
trait TraitA {
184+
type AssocA: TraitB<AssocB: TraitC>;
185+
}
186+
```
187+
188+
is to add the `where` clause, as specified above, to the trait, desugaring to:
189+
190+
```rust
191+
trait TraitA
192+
where
193+
<Self::AssocA as TraitB>::AssocB: TraitC,
194+
{
195+
type AssocA: TraitB;
196+
}
197+
```
198+
199+
However, at the time of this writing, a Rust compiler will treat this
200+
differently than the desugaring proposed in the reference.
201+
The following snippet illustrates the difference:
202+
203+
```rust
204+
trait Foo where <Self::Bar as Iterator>::Item: Copy {
205+
type Bar: Iterator;
206+
}
207+
208+
trait Foo2 {
209+
type Bar: Iterator<Item = Self::BarItem>;
210+
type BarItem: Copy;
211+
}
212+
213+
fn use_foo<X: Foo>(arg: X)
214+
where <X::Bar as Iterator>::Item: Copy
215+
// ^-- Remove this line and it will error with:
216+
// error[E0277]: `<<X as Foo>::Bar as std::iter::Iterator>::Item` doesn't implement `Copy`
217+
{
218+
let item: <X::Bar as Iterator>::Item;
219+
}
220+
221+
fn use_foo2<X: Foo2>(arg: X) {
222+
let item: <X::Bar as Iterator>::Item;
223+
}
224+
```
225+
226+
The desugaring with a `where` therefore becomes problematic from a perspective
227+
of usability.
228+
229+
However, [RFC 2089, Implied Bounds][RFC 2089] specifies that desugaring to the
230+
`where` clause in the trait will permit the `use_foo` function to omit its
231+
`where` clause. This entails that both desugarings become equivalent from the
232+
point of view of a user. The desugaring with `where` therefore becomes viable
233+
in the presence of [RFC 2089].
234+
235+
# Unresolved questions
236+
[unresolved]: #unresolved-questions
237+
238+
- Does allowing this for `dyn` trait objects introduce any unforeseen issues?
239+
This can be resolved during stabilization.
240+
241+
- The exact desugaring in the context of putting bounds on an associated type
242+
of a trait is left unresolved. The semantics should however be preserved.
243+
This is also the case with other desugarings in this RFC.

0 commit comments

Comments
 (0)