|
| 1 | +- Feature Name: associated_type_operators |
| 2 | +- Start Date: 2016-04-29 |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Allow type operators to be associated with traits. This is an incremental step |
| 10 | +toward a more general feature commonly called "higher-kinded types," which is |
| 11 | +often ranked highly as a requested feature by Rust users. This specific feature |
| 12 | +(associated type operators) resolves one of the most common use cases for |
| 13 | +higher-kindedness, is a relatively simple extension to the type system compared |
| 14 | +to other forms of higher-kinded polymorphism, and is forward compatible with |
| 15 | +more complex forms of higher-kinded polymorphism that may be introduced in the |
| 16 | +future. |
| 17 | + |
| 18 | + |
| 19 | +# Motivation |
| 20 | +[motivation]: #motivation |
| 21 | + |
| 22 | +Consider the following trait as a representative motivating example: |
| 23 | + |
| 24 | +```rust |
| 25 | +trait StreamingIterator { |
| 26 | + type Item<'a>; |
| 27 | + fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +This trait is very useful - it allows for a kind of Iterator which yields |
| 32 | +values which have a lifetime tied to the lifetime of the reference passed to |
| 33 | +`next`. A particular obvious use case for this trait would be an iterator over |
| 34 | +a vector which yields overlapping, mutable subslices with each iteration. Using |
| 35 | +the standard `Iterator` interface, such an implementation would be invalid, |
| 36 | +because each slice would be required to exist for as long as the iterator, |
| 37 | +rather than for as long as the borrow initiated by `next`. |
| 38 | + |
| 39 | +This trait cannot be expressed in Rust as it exists today, because it depends |
| 40 | +on a sort of higher-kinded polymorphism. This RFC would extend Rust to include |
| 41 | +that specific form of higher-kinded polymorphism, which is refered to here as |
| 42 | +associated type operators. This feature has a number of applications, but the |
| 43 | +primary application is along the same lines as the `StreamingIterator` trait: |
| 44 | +defining traits which yield types which have a lifetime tied to the local |
| 45 | +borrowing of the receiver type. |
| 46 | + |
| 47 | +# Detailed design |
| 48 | +[design]: #detailed-design |
| 49 | + |
| 50 | +## Background: What is kindedness? |
| 51 | + |
| 52 | +"Higher-kinded types" is a vague term, conflating multiple language features |
| 53 | +under a single inaccurate banner. Let us discuss specifically the notion of a |
| 54 | +'kind' as background for this RFC. Kinds are often called 'the type of a type', |
| 55 | +the exact sort of unhelpful description that only makes sense to someone who |
| 56 | +already understands what is being explained. We'll take a different approach. |
| 57 | + |
| 58 | +In a well-typed language, every expression has a type. Many expressions have |
| 59 | +what are sometimes called 'base types,' types which are primitive to the |
| 60 | +language and which cannot be described in terms of other types. In Rust, the |
| 61 | +types `bool`, `i64`, `usize`, and `char` are all prominent examples of base |
| 62 | +types. In contrast, there are other types which are formed by arranging other |
| 63 | +types - functions are a good example of this. Consider this simple function: |
| 64 | + |
| 65 | +```rust |
| 66 | +fn not(x: bool) -> bool { |
| 67 | + !x |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +`not has the type `bool -> bool` (my apologies for using a syntax different |
| 72 | +from Rust's). Note that this is different from the type of `not(true)`, which |
| 73 | +is `bool`. This difference is important, by way of analogy, to understanding |
| 74 | +higher-kindedness. |
| 75 | + |
| 76 | +In the analysis of kinds, all of these types - `bool`, `char`, `bool -> bool` |
| 77 | +and so on - have the kind `type`, which is often written `*`. This is a base |
| 78 | +kind, just as `bool` is a base type. In contrast, there are more complex kinds, |
| 79 | +such as `* -> *`. An example of an term of this kind is `Vec`, which takes a |
| 80 | +type as a parameter and evalues to a type. The difference between the kind of |
| 81 | +`Vec` and the kind of `Vec<i32>` (which is `*`) is analogous to the difference |
| 82 | +between the type of `not` and `not(true)`. Note that `Vec<T>` has the kind `*`, |
| 83 | +just like `Vec<i32>`: even though `T` is a type parameter, `Vec` is still being |
| 84 | +applied to a type, just like `not(x)` still has the type `bool` even if `x` is |
| 85 | +dynamically determined. |
| 86 | + |
| 87 | +A relatively uncommon feature of Rust is that it has _two_ base kinds, whereas |
| 88 | +many languages which deal with higher-kindedness only have the base kind `*`. |
| 89 | +The other base kind of Rust is the lifetime parameter, which for conveniences |
| 90 | +sake we will represent as `&`. For a type `Foo<'a>`, the kind of `Foo` is |
| 91 | +`& -> *`. |
| 92 | + |
| 93 | +Terms of a higher kind are often called 'type operators'; type operators which |
| 94 | +evaluate to a type are called 'type constructors.' The concept of |
| 95 | +'higher-kinded types' usually refers to the ability to write code which is |
| 96 | +polymorphic over type operators in some way, such as implementing a trait for a |
| 97 | +type operator. This proposal is to allow a type operator to be associated with |
| 98 | +a trait, in the same way that a type or a const can be associated with a trait |
| 99 | +today. |
| 100 | + |
| 101 | +## The basic requirements of associated type operators |
| 102 | + |
| 103 | +Adding associated type operators to the language requires the introduction of |
| 104 | +four discrete constructs: |
| 105 | + |
| 106 | +1. In a definition of a trait, an associated type operator can be declared. |
| 107 | +2. In the position of any type within the definition of that trait, the |
| 108 | + associated type operator can be applied to a type parameter or a concrete |
| 109 | + type which is in scope. |
| 110 | +3. In the implementation of that trait, a type operator of the correct kind can |
| 111 | + be assigned to the declared associated type operator. |
| 112 | +4. When bounding a type parameter by that trait, that trait can be bound to |
| 113 | + have a concrete type operator as this associated type operator. |
| 114 | + |
| 115 | +## Partial application |
| 116 | + |
| 117 | +In order for this feature to be useful, we will have to allow for type |
| 118 | +operators to partially applied. Many languages with higher-kinded polymorphism |
| 119 | +use currying as an alternative to partial application. Rust does not have |
| 120 | +currying at the level of expressions, and currying would not be sufficient |
| 121 | +to enable the use cases that exist for type operators, so this RFC does not |
| 122 | +propose using currying for higher-kinded polymorphism. |
| 123 | + |
| 124 | +As an example, the reference operator has the kind `&, * -> *`, taking both |
| 125 | +a lifetime and a type to produce a new type. With currying, the two parameters |
| 126 | +to the reference operator would have to have a defined order, and it would |
| 127 | +be possible to partially apply only one of the parameters to the reference |
| 128 | +operator. That is, if it were `& -> * -> *`, one could apply it to a lifetime |
| 129 | +to produce a `* -> *` operator, but one could not apply it to a type to produce |
| 130 | +a `& -> *` operator. If it were defined as `* -> & -> *`, it would be |
| 131 | +restricted in the opposite way. Because Rust makes use of two base kinds, |
| 132 | +currying would severely restrict the forms of abstraction enabled by Rust. |
| 133 | + |
| 134 | +Instead, when defining an associated type operator, an anonymous type operator |
| 135 | +can be constructed from a type operator with more parameters by applying any |
| 136 | +of the parameters to that operator. The syntax discussed below makes it |
| 137 | +unambiguous and easy to see which parameters remain undetermined at the point |
| 138 | +of assigning the associated type operator to a concrete type operator. |
| 139 | + |
| 140 | +When used in a type position, of course, all of the parameters to an associated |
| 141 | +type operator must have been applied to concrete types or type parameters that |
| 142 | +are in scope. |
| 143 | + |
| 144 | +## Associated type operators in bounds |
| 145 | + |
| 146 | +This RFC proposes making associated type operators available in bounds only |
| 147 | +as concrete type operators. Because higher-kinded traits cannot be defined, and |
| 148 | +traits cannot be implemented for type operators, it is not possible to bound |
| 149 | +associated type operators by traits. |
| 150 | + |
| 151 | +Even without higher-kinded traits, it could be useful to bound associated type |
| 152 | +operators with some sort of higher-rank syntax, as in: |
| 153 | + |
| 154 | +```rust |
| 155 | +T where T: StreamingIterator, for<'a> T::Item<'a>: Display |
| 156 | +``` |
| 157 | + |
| 158 | +However, this RFC does not propose adding this feature. |
| 159 | + |
| 160 | +## Benefits of implementing only this feature before other higher-kinded polymorphisms |
| 161 | + |
| 162 | +This feature is the first 20% of higher-kinded polymorphism which is worth 50% |
| 163 | +of the full implementation. It is the ideal starting point, as it will enable |
| 164 | +many constructs while adding relatively few complicates to the type system. By |
| 165 | +implementing only associated type operators, we sidestep several issues: |
| 166 | + |
| 167 | +* Defining higher-kinded traits |
| 168 | +* Implementing traits for type operators |
| 169 | +* Higher order type operators |
| 170 | +* Type operator parameters bound by higher-kinded traits |
| 171 | +* Type operator parameters applied to a given type or type parameter |
| 172 | + |
| 173 | +## Proposed syntax |
| 174 | + |
| 175 | +The syntax proposed in this RFC is very similar to the syntax of associated |
| 176 | +types and type aliases. An advantage of this is that users less familiar with |
| 177 | +the intimate details of kindedness will hopefully find this feature intuitive. |
| 178 | + |
| 179 | +To declare an associated type operator, simply declare an associated type |
| 180 | +with parameters on the type name, as in: |
| 181 | + |
| 182 | +```rust |
| 183 | +trait StreamingIterator { |
| 184 | + type Item<'a>; |
| 185 | + ... |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | +Here `Item` is an associated type operator of the kind `& -> *`. |
| 190 | + |
| 191 | +To apply the associated type operator, simply use it in the position where |
| 192 | +a normal type operator would be used instead, as in: |
| 193 | + |
| 194 | +```rust |
| 195 | +trait StreamingIterator { |
| 196 | + ... |
| 197 | + fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +To assign the associated type operator, use the parameters in the type |
| 202 | +declaration on the right-hand side of the type expression, as in: |
| 203 | + |
| 204 | +```rust |
| 205 | +impl<T> StreamingIterator for StreamIter<T> { |
| 206 | + type Item<'a> = &'a [T]; |
| 207 | + ... |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +Note here that a slice reference has the kind `&, * -> *`, but the local type |
| 212 | +parameter `T` is applied to it through partial application to form a type |
| 213 | +operator `& -> *`. The syntax makes it clear that the unapplied parameter is |
| 214 | +the lifetime `'a`, because `'a` is introduced on the type Item. |
| 215 | + |
| 216 | +This has the same appearance as the declaration of type operator aliases which |
| 217 | +are not associated with the trait. |
| 218 | + |
| 219 | +To add a concrete bound as an associated type operator, the syntax is the same |
| 220 | +as adding a concrete bound of an associated type. Here, any types or lifetimes |
| 221 | +which are parameters to the associated type operator are omitted (not elided): |
| 222 | + |
| 223 | +```rust |
| 224 | +where T: StreamingIterator<Item=&[u8]> |
| 225 | +``` |
| 226 | + |
| 227 | +`&[u8]` is not an elided form of some `&'a [u8]`, but a type operator of the |
| 228 | +kind `& -> *`. |
| 229 | + |
| 230 | + |
| 231 | +However, life time parameters can be elided when applied to associated type |
| 232 | +operators in the type position just as they can be elided for concrete type |
| 233 | +operators, as in this case, providing a full definition of `StreamingIterator`: |
| 234 | + |
| 235 | +```rust |
| 236 | +trait StreamingIterator { |
| 237 | + type Item<'a>; |
| 238 | + fn next(&mut self) -> Option<Self::Item>; |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | + |
| 243 | +# Drawbacks |
| 244 | +[drawbacks]: #drawbacks |
| 245 | + |
| 246 | +## Drawbacks to the concept |
| 247 | + |
| 248 | +This adds complexity to the language, and implements a part of higher-kinded |
| 249 | +polymorphism without all of the benefits that come along with it. There are |
| 250 | +valid arguments in favor of waiting until additional forms of higher-kinded |
| 251 | +polymorphism have been worked out, as well as in favor of never implementing |
| 252 | +higher-kinded polymorphism at all. |
| 253 | + |
| 254 | +## Drawbacks to the syntax |
| 255 | + |
| 256 | +Though this syntax is a natural fit for associated type operators, it is not |
| 257 | +a natural syntax for other forms of higher-kinded polymorphism. As a result, |
| 258 | +the syntaxes of two related forms of polymorphism will be significantly |
| 259 | +different. We believe this cost is justified by the advantages of making the |
| 260 | +syntax similar to associated types. |
| 261 | + |
| 262 | +# Alternatives |
| 263 | +[alternatives]: #alternatives |
| 264 | + |
| 265 | +An alternative is to push harder on higher-ranked lifetimes, possibly |
| 266 | +introducing some elision that would make them easier to use. |
| 267 | + |
| 268 | +Currently, an approximation of `StreamingIterator` can be defined like this: |
| 269 | + |
| 270 | +```rust |
| 271 | +trait StreamingIterator<'a> { |
| 272 | + type Item: 'a; |
| 273 | + fn next(&'a self) -> Option<Self::Item>; |
| 274 | +} |
| 275 | +``` |
| 276 | + |
| 277 | +You can then bound types as `T: for<'a> StreamingIterator<'a>` to avoid the |
| 278 | +lifetime parameter infecting everything `StreamingIterator` appears. |
| 279 | + |
| 280 | +However, this only partially prevents the infectiveness of `StreamingIterator`, |
| 281 | +only allows for some of the types that associated type operators can express, |
| 282 | +and is in generally a hacky attempt to work around the limitation rather than |
| 283 | +an equivalent alternative. |
| 284 | + |
| 285 | +# Unresolved questions |
| 286 | +[unresolved]: #unresolved-questions |
| 287 | + |
| 288 | +This design does not resolve the question of introducing more advanced forms of |
| 289 | +higher-kinded polymorphism. This document does not describe the details of |
| 290 | +implementing this RFC in terms of rustc's current typeck, because the author |
| 291 | +is not familiar with that code. This document is certainly inadequate in its |
| 292 | +description of this feature, most likely in relation to partial application, |
| 293 | +because of the author's ignorance and personal defects. |
0 commit comments