|
| 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