From c6977b99194893976ee22432a560f926bfbf44d1 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Fri, 29 Apr 2016 22:29:08 -0700 Subject: [PATCH 01/10] Associated type operators (a form of higher-kinded polymorphism). --- 0000-friends_in_high_kindednesses.md | 293 +++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 0000-friends_in_high_kindednesses.md diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md new file mode 100644 index 00000000000..bb74d560d58 --- /dev/null +++ b/0000-friends_in_high_kindednesses.md @@ -0,0 +1,293 @@ +- Feature Name: associated_type_operators +- Start Date: 2016-04-29 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow type operators to be associated with traits. This is an incremental step +toward a more general feature commonly called "higher-kinded types," which is +often ranked highly as a requested feature by Rust users. This specific feature +(associated type operators) resolves one of the most common use cases for +higher-kindedness, is a relatively simple extension to the type system compared +to other forms of higher-kinded polymorphism, and is forward compatible with +more complex forms of higher-kinded polymorphism that may be introduced in the +future. + + +# Motivation +[motivation]: #motivation + +Consider the following trait as a representative motivating example: + +```rust +trait StreamingIterator { + type Item<'a>; + fn next<'a>(&'a mut self) -> Option>; +} +``` + +This trait is very useful - it allows for a kind of Iterator which yields +values which have a lifetime tied to the lifetime of the reference passed to +`next`. A particular obvious use case for this trait would be an iterator over +a vector which yields overlapping, mutable subslices with each iteration. Using +the standard `Iterator` interface, such an implementation would be invalid, +because each slice would be required to exist for as long as the iterator, +rather than for as long as the borrow initiated by `next`. + +This trait cannot be expressed in Rust as it exists today, because it depends +on a sort of higher-kinded polymorphism. This RFC would extend Rust to include +that specific form of higher-kinded polymorphism, which is refered to here as +associated type operators. This feature has a number of applications, but the +primary application is along the same lines as the `StreamingIterator` trait: +defining traits which yield types which have a lifetime tied to the local +borrowing of the receiver type. + +# Detailed design +[design]: #detailed-design + +## Background: What is kindedness? + +"Higher-kinded types" is a vague term, conflating multiple language features +under a single inaccurate banner. Let us discuss specifically the notion of a +'kind' as background for this RFC. Kinds are often called 'the type of a type', +the exact sort of unhelpful description that only makes sense to someone who +already understands what is being explained. We'll take a different approach. + +In a well-typed language, every expression has a type. Many expressions have +what are sometimes called 'base types,' types which are primitive to the +language and which cannot be described in terms of other types. In Rust, the +types `bool`, `i64`, `usize`, and `char` are all prominent examples of base +types. In contrast, there are other types which are formed by arranging other +types - functions are a good example of this. Consider this simple function: + +```rust +fn not(x: bool) -> bool { + !x +} +``` + +`not has the type `bool -> bool` (my apologies for using a syntax different +from Rust's). Note that this is different from the type of `not(true)`, which +is `bool`. This difference is important, by way of analogy, to understanding +higher-kindedness. + +In the analysis of kinds, all of these types - `bool`, `char`, `bool -> bool` +and so on - have the kind `type`, which is often written `*`. This is a base +kind, just as `bool` is a base type. In contrast, there are more complex kinds, +such as `* -> *`. An example of an term of this kind is `Vec`, which takes a +type as a parameter and evalues to a type. The difference between the kind of +`Vec` and the kind of `Vec` (which is `*`) is analogous to the difference +between the type of `not` and `not(true)`. Note that `Vec` has the kind `*`, +just like `Vec`: even though `T` is a type parameter, `Vec` is still being +applied to a type, just like `not(x)` still has the type `bool` even if `x` is +dynamically determined. + +A relatively uncommon feature of Rust is that it has _two_ base kinds, whereas +many languages which deal with higher-kindedness only have the base kind `*`. +The other base kind of Rust is the lifetime parameter, which for conveniences +sake we will represent as `&`. For a type `Foo<'a>`, the kind of `Foo` is +`& -> *`. + +Terms of a higher kind are often called 'type operators'; type operators which +evaluate to a type are called 'type constructors.' The concept of +'higher-kinded types' usually refers to the ability to write code which is +polymorphic over type operators in some way, such as implementing a trait for a +type operator. This proposal is to allow a type operator to be associated with +a trait, in the same way that a type or a const can be associated with a trait +today. + +## The basic requirements of associated type operators + +Adding associated type operators to the language requires the introduction of +four discrete constructs: + +1. In a definition of a trait, an associated type operator can be declared. +2. In the position of any type within the definition of that trait, the + associated type operator can be applied to a type parameter or a concrete + type which is in scope. +3. In the implementation of that trait, a type operator of the correct kind can + be assigned to the declared associated type operator. +4. When bounding a type parameter by that trait, that trait can be bound to + have a concrete type operator as this associated type operator. + +## Partial application + +In order for this feature to be useful, we will have to allow for type +operators to partially applied. Many languages with higher-kinded polymorphism +use currying as an alternative to partial application. Rust does not have +currying at the level of expressions, and currying would not be sufficient +to enable the use cases that exist for type operators, so this RFC does not +propose using currying for higher-kinded polymorphism. + +As an example, the reference operator has the kind `&, * -> *`, taking both +a lifetime and a type to produce a new type. With currying, the two parameters +to the reference operator would have to have a defined order, and it would +be possible to partially apply only one of the parameters to the reference +operator. That is, if it were `& -> * -> *`, one could apply it to a lifetime +to produce a `* -> *` operator, but one could not apply it to a type to produce +a `& -> *` operator. If it were defined as `* -> & -> *`, it would be +restricted in the opposite way. Because Rust makes use of two base kinds, +currying would severely restrict the forms of abstraction enabled by Rust. + +Instead, when defining an associated type operator, an anonymous type operator +can be constructed from a type operator with more parameters by applying any +of the parameters to that operator. The syntax discussed below makes it +unambiguous and easy to see which parameters remain undetermined at the point +of assigning the associated type operator to a concrete type operator. + +When used in a type position, of course, all of the parameters to an associated +type operator must have been applied to concrete types or type parameters that +are in scope. + +## Associated type operators in bounds + +This RFC proposes making associated type operators available in bounds only +as concrete type operators. Because higher-kinded traits cannot be defined, and +traits cannot be implemented for type operators, it is not possible to bound +associated type operators by traits. + +Even without higher-kinded traits, it could be useful to bound associated type +operators with some sort of higher-rank syntax, as in: + +```rust +T where T: StreamingIterator, for<'a> T::Item<'a>: Display +``` + +However, this RFC does not propose adding this feature. + +## Benefits of implementing only this feature before other higher-kinded polymorphisms + +This feature is the first 20% of higher-kinded polymorphism which is worth 50% +of the full implementation. It is the ideal starting point, as it will enable +many constructs while adding relatively few complicates to the type system. By +implementing only associated type operators, we sidestep several issues: + +* Defining higher-kinded traits +* Implementing traits for type operators +* Higher order type operators +* Type operator parameters bound by higher-kinded traits +* Type operator parameters applied to a given type or type parameter + +## Proposed syntax + +The syntax proposed in this RFC is very similar to the syntax of associated +types and type aliases. An advantage of this is that users less familiar with +the intimate details of kindedness will hopefully find this feature intuitive. + +To declare an associated type operator, simply declare an associated type +with parameters on the type name, as in: + +```rust +trait StreamingIterator { + type Item<'a>; + ... +} +``` + +Here `Item` is an associated type operator of the kind `& -> *`. + +To apply the associated type operator, simply use it in the position where +a normal type operator would be used instead, as in: + +```rust +trait StreamingIterator { + ... + fn next<'a>(&'a mut self) -> Option>; +} +``` + +To assign the associated type operator, use the parameters in the type +declaration on the right-hand side of the type expression, as in: + +```rust +impl StreamingIterator for StreamIter { + type Item<'a> = &'a [T]; + ... +} +``` + +Note here that a slice reference has the kind `&, * -> *`, but the local type +parameter `T` is applied to it through partial application to form a type +operator `& -> *`. The syntax makes it clear that the unapplied parameter is +the lifetime `'a`, because `'a` is introduced on the type Item. + +This has the same appearance as the declaration of type operator aliases which +are not associated with the trait. + +To add a concrete bound as an associated type operator, the syntax is the same +as adding a concrete bound of an associated type. Here, any types or lifetimes +which are parameters to the associated type operator are omitted (not elided): + +```rust +where T: StreamingIterator +``` + +`&[u8]` is not an elided form of some `&'a [u8]`, but a type operator of the +kind `& -> *`. + + +However, life time parameters can be elided when applied to associated type +operators in the type position just as they can be elided for concrete type +operators, as in this case, providing a full definition of `StreamingIterator`: + +```rust +trait StreamingIterator { + type Item<'a>; + fn next(&mut self) -> Option; +} +``` + + +# Drawbacks +[drawbacks]: #drawbacks + +## Drawbacks to the concept + +This adds complexity to the language, and implements a part of higher-kinded +polymorphism without all of the benefits that come along with it. There are +valid arguments in favor of waiting until additional forms of higher-kinded +polymorphism have been worked out, as well as in favor of never implementing +higher-kinded polymorphism at all. + +## Drawbacks to the syntax + +Though this syntax is a natural fit for associated type operators, it is not +a natural syntax for other forms of higher-kinded polymorphism. As a result, +the syntaxes of two related forms of polymorphism will be significantly +different. We believe this cost is justified by the advantages of making the +syntax similar to associated types. + +# Alternatives +[alternatives]: #alternatives + +An alternative is to push harder on higher-ranked lifetimes, possibly +introducing some elision that would make them easier to use. + +Currently, an approximation of `StreamingIterator` can be defined like this: + +```rust +trait StreamingIterator<'a> { + type Item: 'a; + fn next(&'a self) -> Option; +} +``` + +You can then bound types as `T: for<'a> StreamingIterator<'a>` to avoid the +lifetime parameter infecting everything `StreamingIterator` appears. + +However, this only partially prevents the infectiveness of `StreamingIterator`, +only allows for some of the types that associated type operators can express, +and is in generally a hacky attempt to work around the limitation rather than +an equivalent alternative. + +# Unresolved questions +[unresolved]: #unresolved-questions + +This design does not resolve the question of introducing more advanced forms of +higher-kinded polymorphism. This document does not describe the details of +implementing this RFC in terms of rustc's current typeck, because the author +is not familiar with that code. This document is certainly inadequate in its +description of this feature, most likely in relation to partial application, +because of the author's ignorance and personal defects. From 04451dd937ca28def67054fca9afb9e33001a5e7 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sun, 1 May 2016 17:33:27 -0700 Subject: [PATCH 02/10] Improve RFC's content in re ongoing discussion. --- 0000-friends_in_high_kindednesses.md | 497 ++++++++++++++++++--------- 1 file changed, 341 insertions(+), 156 deletions(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index bb74d560d58..bd31e696ab0 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -1,4 +1,4 @@ -- Feature Name: associated_type_operators +- Feature Name: associated_type_constructors - Start Date: 2016-04-29 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) @@ -6,14 +6,14 @@ # Summary [summary]: #summary -Allow type operators to be associated with traits. This is an incremental step -toward a more general feature commonly called "higher-kinded types," which is -often ranked highly as a requested feature by Rust users. This specific feature -(associated type operators) resolves one of the most common use cases for -higher-kindedness, is a relatively simple extension to the type system compared -to other forms of higher-kinded polymorphism, and is forward compatible with -more complex forms of higher-kinded polymorphism that may be introduced in the -future. +Allow type constructors to be associated with traits. This is an incremental +step toward a more general feature commonly called "higher-kinded types," which +is often ranked highly as a requested feature by Rust users. This specific +feature (associated type constructors) resolves one of the most common use +cases for higher-kindedness, is a relatively simple extension to the type +system compared to other forms of higher-kinded polymorphism, and is forward +compatible with more complex forms of higher-kinded polymorphism that may be +introduced in the future. # Motivation @@ -39,10 +39,10 @@ rather than for as long as the borrow initiated by `next`. This trait cannot be expressed in Rust as it exists today, because it depends on a sort of higher-kinded polymorphism. This RFC would extend Rust to include that specific form of higher-kinded polymorphism, which is refered to here as -associated type operators. This feature has a number of applications, but the -primary application is along the same lines as the `StreamingIterator` trait: -defining traits which yield types which have a lifetime tied to the local -borrowing of the receiver type. +associated type constructors. This feature has a number of applications, but +the primary application is along the same lines as the `StreamingIterator` +trait: defining traits which yield types which have a lifetime tied to the +local borrowing of the receiver type. # Detailed design [design]: #detailed-design @@ -50,17 +50,18 @@ borrowing of the receiver type. ## Background: What is kindedness? "Higher-kinded types" is a vague term, conflating multiple language features -under a single inaccurate banner. Let us discuss specifically the notion of a -'kind' as background for this RFC. Kinds are often called 'the type of a type', -the exact sort of unhelpful description that only makes sense to someone who -already understands what is being explained. We'll take a different approach. +under a single banner, which can be inaccurate. As background, this RFC +includes a brief overview of the notion of kinds and kindedness. Kinds are +often called 'the type of a type,' the exact sort of unhelpful description that +only makes sense to someone who already understands what is being explained. +Instead, let's try to understand kinds by analogy to types. In a well-typed language, every expression has a type. Many expressions have what are sometimes called 'base types,' types which are primitive to the language and which cannot be described in terms of other types. In Rust, the types `bool`, `i64`, `usize`, and `char` are all prominent examples of base -types. In contrast, there are other types which are formed by arranging other -types - functions are a good example of this. Consider this simple function: +types. In contrast, there are types which are formed by arranging other types - +functions are a good example of this. Consider this simple function: ```rust fn not(x: bool) -> bool { @@ -68,202 +69,376 @@ fn not(x: bool) -> bool { } ``` -`not has the type `bool -> bool` (my apologies for using a syntax different +`not` has the type `bool -> bool` (my apologies for using a syntax different from Rust's). Note that this is different from the type of `not(true)`, which -is `bool`. This difference is important, by way of analogy, to understanding -higher-kindedness. +is `bool`. This difference is important to understanding higher-kindedness. In the analysis of kinds, all of these types - `bool`, `char`, `bool -> bool` -and so on - have the kind `type`, which is often written `*`. This is a base -kind, just as `bool` is a base type. In contrast, there are more complex kinds, -such as `* -> *`. An example of an term of this kind is `Vec`, which takes a -type as a parameter and evalues to a type. The difference between the kind of -`Vec` and the kind of `Vec` (which is `*`) is analogous to the difference -between the type of `not` and `not(true)`. Note that `Vec` has the kind `*`, -just like `Vec`: even though `T` is a type parameter, `Vec` is still being -applied to a type, just like `not(x)` still has the type `bool` even if `x` is -dynamically determined. +and so on - have the kind `type`. Every type has the kind `type`. However, +`type` is a base kind, just as `bool` is a base type, and there are terms with +more complex kinds, such as `type -> type`. An example of a term of this kind +is `Vec`, which takes a type as a parameter and evaluates to a type. The +difference between the kind of `Vec` and the kind of `Vec` (which is +`type`) is analogous to the difference between the type of `not` and +`not(true)`. Note that `Vec` has the kind `type`, just like `Vec`: even +though `T` is a type parameter, `Vec` is still being applied to a type, just +like `not(x)` still has the type `bool` even though `x` is a variable. A relatively uncommon feature of Rust is that it has _two_ base kinds, whereas -many languages which deal with higher-kindedness only have the base kind `*`. -The other base kind of Rust is the lifetime parameter, which for conveniences -sake we will represent as `&`. For a type `Foo<'a>`, the kind of `Foo` is -`& -> *`. - -Terms of a higher kind are often called 'type operators'; type operators which -evaluate to a type are called 'type constructors.' The concept of -'higher-kinded types' usually refers to the ability to write code which is -polymorphic over type operators in some way, such as implementing a trait for a -type operator. This proposal is to allow a type operator to be associated with -a trait, in the same way that a type or a const can be associated with a trait -today. - -## The basic requirements of associated type operators - -Adding associated type operators to the language requires the introduction of -four discrete constructs: - -1. In a definition of a trait, an associated type operator can be declared. -2. In the position of any type within the definition of that trait, the - associated type operator can be applied to a type parameter or a concrete - type which is in scope. -3. In the implementation of that trait, a type operator of the correct kind can - be assigned to the declared associated type operator. -4. When bounding a type parameter by that trait, that trait can be bound to - have a concrete type operator as this associated type operator. - -## Partial application +many languages which deal with higher-kindedness only have the base kind +`type`. The other base kind of Rust is the lifetime parameter. If you have a +type like `Foo<'a>`, the kind of `Foo` is `lifetime -> type`. -In order for this feature to be useful, we will have to allow for type -operators to partially applied. Many languages with higher-kinded polymorphism -use currying as an alternative to partial application. Rust does not have -currying at the level of expressions, and currying would not be sufficient -to enable the use cases that exist for type operators, so this RFC does not -propose using currying for higher-kinded polymorphism. - -As an example, the reference operator has the kind `&, * -> *`, taking both -a lifetime and a type to produce a new type. With currying, the two parameters -to the reference operator would have to have a defined order, and it would -be possible to partially apply only one of the parameters to the reference -operator. That is, if it were `& -> * -> *`, one could apply it to a lifetime -to produce a `* -> *` operator, but one could not apply it to a type to produce -a `& -> *` operator. If it were defined as `* -> & -> *`, it would be -restricted in the opposite way. Because Rust makes use of two base kinds, -currying would severely restrict the forms of abstraction enabled by Rust. - -Instead, when defining an associated type operator, an anonymous type operator -can be constructed from a type operator with more parameters by applying any -of the parameters to that operator. The syntax discussed below makes it -unambiguous and easy to see which parameters remain undetermined at the point -of assigning the associated type operator to a concrete type operator. +Higher-kinded terms can take multiple arguments as well, of course. `Result` +has the kind `type, type -> type`. Given `vec::Iter<'a, T>` `vec::Iter` has the +kind `lifetime, type -> type`. -When used in a type position, of course, all of the parameters to an associated -type operator must have been applied to concrete types or type parameters that -are in scope. +Terms of a higher kind are often called 'type operators'; the type operators +which evaluate to a type are called 'type constructors'. There are other type +operators which evaluate to other type operators, and there are even higher +order type operators, which take type operators as their argument (so they have +a kind like `(type -> type) -> type`). This RFC doesn't deal with anything as +exotic as that. + +Specifically, the goal of this RFC is to allow type constructors to be +associated with traits, just as you can currently associate functions, types, +and consts with traits. There are other forms of polymorphism involving type +constructors, such as implementing traits for a type constructor instead of a +type, which are not a part of this RFC. -## Associated type operators in bounds +## Features of associated type constructors -This RFC proposes making associated type operators available in bounds only -as concrete type operators. Because higher-kinded traits cannot be defined, and -traits cannot be implemented for type operators, it is not possible to bound -associated type operators by traits. +### Declaring an associated type constructor -Even without higher-kinded traits, it could be useful to bound associated type -operators with some sort of higher-rank syntax, as in: +This RFC proposes a very simple syntax for defining an associated type +constructor, which looks a lot like the syntax for creating aliases for type +constructors. The goal of using this syntax is to avoid to creating roadblocks +for users who do not already understand higher kindedness. ```rust -T where T: StreamingIterator, for<'a> T::Item<'a>: Display +trait StreamingIterator { + type Item<'a>; +} ``` -However, this RFC does not propose adding this feature. +Here, it is clear that `Item` is a type constructor, because it carries a +parameter. Associated type constructors can carry any number of type and +lifetime parameters, as in: -## Benefits of implementing only this feature before other higher-kinded polymorphisms +```rust +trait FooBar { + type Baz<'a, T, U>; +} +``` -This feature is the first 20% of higher-kinded polymorphism which is worth 50% -of the full implementation. It is the ideal starting point, as it will enable -many constructs while adding relatively few complicates to the type system. By -implementing only associated type operators, we sidestep several issues: +Associated type constructors can be followed by `where` clauses, which place +trait bounds on the types constructed by this constructor. For example: -* Defining higher-kinded traits -* Implementing traits for type operators -* Higher order type operators -* Type operator parameters bound by higher-kinded traits -* Type operator parameters applied to a given type or type parameter +```rust +trait Collection { + type Iter<'a> where for<'a> Self::Iter<'a>: Iterator; + type IterMut<'a> where for<'a> Self::IterMut<'a>: Iterator; + type IntoIter: Iterator; +} +``` + +A `where` clause is used to avoid the impression that this is providing a +bound on the constructor itself. Note the contrast to `IntoIter`, which is +not a type constructor. Also note that this involves an extension to HRTB, +which is discussed later in this RFC. -## Proposed syntax +As a last note, these `where` clauses do not need to involve HRTB, but can +instead apply type/lifetime parameters or concrete types/lifetimes that are +in scope to the type constructor, as in: -The syntax proposed in this RFC is very similar to the syntax of associated -types and type aliases. An advantage of this is that users less familiar with -the intimate details of kindedness will hopefully find this feature intuitive. +```rust +trait Foo { + type Bar where Self::Bar: Display; + type Baz<'a> where Self::Baz<'static>: Send; +} +``` -To declare an associated type operator, simply declare an associated type -with parameters on the type name, as in: +### Assigning an associated type constructor + +Assigning associated type constructors in impls is very similar to the syntax +for assigning associated types: ```rust -trait StreamingIterator { - type Item<'a>; +impl StreamingIterator for StreamIterMut { + type Item<'a> = &'a mut [T]; ... } ``` -Here `Item` is an associated type operator of the kind `& -> *`. +Note that this example makes use of partial application (see the later section +on partial application for more information about this feature). The parameter +to this argument is quite clear, because it is the argument associated with +the type constructor. If there were multiple lifetimes involved, it would still +be unambiguous which was being applied and which isn't, for example: -To apply the associated type operator, simply use it in the position where -a normal type operator would be used instead, as in: +```rust +impl<'a> StreamingIterator for FooStreamIter<'a> { + type Item<'b> = &'b mut [Foo<'a>]; +} +``` + +### Using an associated type constructor to construct a type + +Once a trait has an associated type constructor, it can be applied to any +type/lifetime parameters or concrete types/lifetimes that are in scope. This +can be done both inside the body of the trait and outside of it, using syntax +which is analogous to the syntax for using associated types. Here are some +examples: ```rust trait StreamingIterator { - ... - fn next<'a>(&'a mut self) -> Option>; + type Item<'a>; + // Applying the lifetime parameter `'a` to `Self::Item` inside the trait. + fn next<'a>(&'a self) -> Option>; +} + +struct Foo { + // Applying a concrete lifetime to the constructor outside the trait. + bar: ::Item<'static>; } ``` -To assign the associated type operator, use the parameters in the type -declaration on the right-hand side of the type expression, as in: +Associated type constructors can also be used to construct other type +constructors through partial application (see the later section on partial +application for more information about this feature). ```rust -impl StreamingIterator for StreamIter { - type Item<'a> = &'a [T]; - ... +trait Foo { + type Bar<'a, T>; +} + +trait Baz { + type Quux<'a>; +} + +impl Baz for T where T: Foo { + type Quux<'a> = ::Bar<'a, usize>; +} +``` + +Lastly, lifetimes can be elided in associated type constructors in the same +manner that they can be elided in other type constructors. Considering lifetime +ellision, the full definition of `StreamingIterator` is: + +```rust +trait StreamingIterator { + type Item<'a>; + fn next(&mut self) -> Option; } ``` -Note here that a slice reference has the kind `&, * -> *`, but the local type -parameter `T` is applied to it through partial application to form a type -operator `& -> *`. The syntax makes it clear that the unapplied parameter is -the lifetime `'a`, because `'a` is introduced on the type Item. +### Using associated type constructors in bounds + +Users can bound parameters by the type constructed by that trait's associated +type constructor of a trait using HRTB. Both type equality bounds and trait +bounds of this kind are valid: + +```rust +fn foo StreamingIterator=&'a [i32]>>(iter: T) { ... } + +fn foo(iter: T) where T: StreamingIterator, for<'a> T::Item<'a>: Display { ... } +``` + +See the section on extending HRTBs for more information about that aspect of +this feature. + +This RFC does not propose allowing any sort of bound by the type constructor +itself, whether an equality bound or a trait bound (trait bounds of course are +also impossible). That is, one can do the former but not the latter here: + +```rust +// Valid +fn foo Foo=Vec>>(x: T) { ... } + +// Invalid +fn foo>(x: T) { ... } +``` + +HRTBs allow us to express the same bounds without adding quite as radical a +new feature as adding bounds by equality of type constructors. + +## Partial Application + +In order for this feature to be useful, we will have to allow for type +constructors to partially applied. Many languages with higher-kinded +polymorphism use currying as an alternative to partial application. Rust does +not have currying at the level of expressions, and currying would not be +sufficient to enable the use cases that exist for type constructors, so this +RFC does not propose using currying for higher-kinded polymorphism. + +As an example, the reference operator has the kind `lifetime, type -> type`, +taking both a lifetime and a type to produce a new type. With currying, the two +parameters to the reference operator would have to have a defined order, and it +would be possible to partially apply only one of the parameters to the +reference operator. That is, if it were `lifetime -> type -> type`, one could +apply it to a lifetime to produce a `type -> type` operator, but one could not +apply it to a type to produce a `lifetime -> type` operator. If it were defined +as `type -> lifetime -> type`, it would be restricted in the opposite way. +Because Rust makes use of two base kinds, currying would severely restrict the +forms of abstraction enabled by Rust. + +Instead, when defining an associated type constructor, an anonymous type +constructor can be constructed from a type constructor with more parameters by +applying any of the parameters to that type constructor. The syntax discussed +below makes it unambiguous and easy to see which parameters remain undetermined +at the point of assigning the associated type constructor to a concrete type +constructor. + +When used in a type position, of course, all of the parameters to an associated +type constructor must have been applied to concrete types or type parameters that +are in scope, so that it can be evaluated to a proper type. -This has the same appearance as the declaration of type operator aliases which -are not associated with the trait. +## Extending HRTBs -To add a concrete bound as an associated type operator, the syntax is the same -as adding a concrete bound of an associated type. Here, any types or lifetimes -which are parameters to the associated type operator are omitted (not elided): +Providing bounds on the types constructed by associated type constructors +requires heavy use of HRTBs, or higher-ranked trait bounds. This exists in Rust +today, but in a limited form, and it is an obscure feature primarily used in +the background to make function traits behave as expected. + +In brief, a higher-ranked trait bound is one in which a type or lifetime +parameter is introduced only for the scope of that trait bound. A classic +example of how this can be useful is in the contrast between these two +functions: ```rust -where T: StreamingIterator +fn foo1(x: T, id: F) -> T where F: Fn(T) -> T { + id(x) +} + +fn foo2(x: T, id: F) -> T where F: for Fn(X) -> X { + id(x) +} + +// Valid (evaluates to 4) +foo1::(2, |x| x + x) + +// Invalid (type error) +foo2::(2, |x| x + x) ``` -`&[u8]` is not an elided form of some `&'a [u8]`, but a type operator of the -kind `& -> *`. +In the second function, we _guarantee_ that the `id` argument is the identity +function (ignoring side effects), because it must be a valid function of `X` +to `X` for _all_ `X`, whereas the first can be specialized to only be a valid +function for the type `T`, in this case `i32`. +Higher-ranked trait bounds have several other use cases. Currently, Rust uses +them to declare that arguments with different lifetimes can be passed to +function types, by requiring that that function be valid for all lifetimes, +rather than just for some single lifetime parameter. -However, life time parameters can be elided when applied to associated type -operators in the type position just as they can be elided for concrete type -operators, as in this case, providing a full definition of `StreamingIterator`: +In order to bound associated type constructors, we use higher-ranked types to +require that the type constructor constructs type which meet some bound. This +can be done both in the declaration and when bounding a type parameter by a +trait, and can be both a trait bound and a type equality bound. Here are +examples in code, with their meanings written out: ```rust -trait StreamingIterator { - type Item<'a>; - fn next(&mut self) -> Option; +trait Sequence { + // For every lifetime, this constructor applied to that lifetime must + // produce a type which is an iterator of references of that lifetime + type Iter<'a> where for<'a> Iter<'a>: Iterator; +} + +// For every lifetime, the associated type constructor Item applied to +// that lifetime produces a reference of that lifetime to a slice of bytes. +struct Foo where T: for<'a> StreamingIterator { + ... } ``` +Enabling this requires extending HRTBs to support type parameters as well as +lifetime parameters. This would also imply that HRTBs could introduce type +parameters that themselves have bounds. The syntax for this is left to another +RFC. + +## Benefits of implementing only this feature before other higher-kinded polymorphisms + +This feature is not full-blown higher-kinded polymorphism, and does not allow +for the forms of abstraction that are so popular in Haskell, but it does +provide most of the unique-to-Rust use cases for higher-kinded polymorphism, +such as streaming iterators and collection traits. It is probably also the +most accessible feature for most users, being somewhat easy to understand +intuitively without understanding higher-kindedness. + +This feature has several tricky implementation challenges, but avoids all of +these features that other kinds of higher-kinded polymorphism require: + +* Defining higher-kinded traits +* Implementing higher-kinded traits for type operators +* Higher order type operators +* Type operator parameters bound by higher-kinded traits +* Type operator parameters applied to a given type or type parameter + +## Advantages of proposed syntax + +The advantage of the proposed syntax is that it leverages syntax that already +exists. Type constructors can already be aliased in Rust using the same syntax +that this used, and while type aliases play no polymorphic role in type +resolution, to users they seem very similar to associated types. A goal of this +syntax is that many users will be able to use types which have assocaited type +constructors without even being aware that this has something to do with a type +system feature called higher-kindedness. # Drawbacks [drawbacks]: #drawbacks -## Drawbacks to the concept +## Adding language complexity + +This would add a somewhat complex feature to the language, being able to +polymorphically resolve type constructors, and requires several extensions to +the type system which make the implementation more complicated. + +Additionally, though the syntax is designed to make this feature easy to learn, +it also makes it more plausible that a user may accidentally use it when they +mean something else, similar to the confusion between `impl .. for Trait` and +`impl .. for T where T: Trait`. For example: + +```rust +// The user means this +trait Foo<'a> { + type Bar: 'a; +} + +// But they write this +trait Foo<'a> { + type Bar<'a>; +} +``` + +## Not full "higher-kinded types" + +This does not add all of the features people want when they talk about higher- +kinded types. For example, it does not enable traits like `Monad`. Some people +may prefer to implement all of these features together at once. However, this +feature is forward compatible with other kinds of higher-kinded polymorphism, +and doesn't preclude implementing them in any way. In fact, it paves the way +by solving some implementation details that will impact other kinds of higher- +kindedness as well, such as partial application. -This adds complexity to the language, and implements a part of higher-kinded -polymorphism without all of the benefits that come along with it. There are -valid arguments in favor of waiting until additional forms of higher-kinded -polymorphism have been worked out, as well as in favor of never implementing -higher-kinded polymorphism at all. +## Syntax isn't like other forms of higher-kinded polymorphism -## Drawbacks to the syntax +Though the proposed syntax is very similar to the syntax for associated types +and type aliases, it is probably not possible for other forms of higher-kinded +polymorphism to use a syntax along the same lines. For this reason, the syntax +used to define an associated type constructor will probably be very different +from the syntax used to e.g. implement a trait for a type constructor. -Though this syntax is a natural fit for associated type operators, it is not -a natural syntax for other forms of higher-kinded polymorphism. As a result, -the syntaxes of two related forms of polymorphism will be significantly -different. We believe this cost is justified by the advantages of making the -syntax similar to associated types. +However, the syntax used for these other forms of higher-kinded polymorphism +will depend on exactly what features they enable. It would be hard to design +a syntax which is consistent with unknown features. # Alternatives [alternatives]: #alternatives -An alternative is to push harder on higher-ranked lifetimes, possibly -introducing some elision that would make them easier to use. +## Push HRTBs harder without associated type constructors + +An alternative is to push harder on HRTBs, possibly introducing some elision +that would make them easier to use. Currently, an approximation of `StreamingIterator` can be defined like this: @@ -278,9 +453,19 @@ You can then bound types as `T: for<'a> StreamingIterator<'a>` to avoid the lifetime parameter infecting everything `StreamingIterator` appears. However, this only partially prevents the infectiveness of `StreamingIterator`, -only allows for some of the types that associated type operators can express, -and is in generally a hacky attempt to work around the limitation rather than -an equivalent alternative. +only allows for some of the types that associated type constructors can +express, and is in generally a hacky attempt to work around the limitation +rather than an equivalent alternative. + +## Only add associated type constructors whose arguments are lifetimes + +If associated type constructors could only take lifetime arguments, much of the +work extending HRTBs would not be necessary. Associated type constructors with +lifetime parameters only covers the primary known use cases for this feature. +Though it is inelegant to treat lifetime parameters differently from type +parameters here, at least as an implementation strategy it may make sense to +first implement this feature with lifetime parameters, and later extend it to +type parameters as well. # Unresolved questions [unresolved]: #unresolved-questions From 01b1510be075349c6fc4d97a98ee2a45ed297778 Mon Sep 17 00:00:00 2001 From: w/o boats Date: Wed, 16 Nov 2016 18:32:35 -0800 Subject: [PATCH 03/10] Update RFC in light of recent discussions --- 0000-friends_in_high_kindednesses.md | 260 ++++++++++++--------------- 1 file changed, 112 insertions(+), 148 deletions(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index bd31e696ab0..21bcb0fbe97 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -108,7 +108,7 @@ type, which are not a part of this RFC. ## Features of associated type constructors -### Declaring an associated type constructor +### Declaring & assigning an associated type constructor This RFC proposes a very simple syntax for defining an associated type constructor, which looks a lot like the syntax for creating aliases for type @@ -121,44 +121,31 @@ trait StreamingIterator { } ``` -Here, it is clear that `Item` is a type constructor, because it carries a -parameter. Associated type constructors can carry any number of type and -lifetime parameters, as in: +It is clear that the `Item` associated item is a type constructor, rather than +a type, because it has a type parameter attached to it. -```rust -trait FooBar { - type Baz<'a, T, U>; -} -``` - -Associated type constructors can be followed by `where` clauses, which place -trait bounds on the types constructed by this constructor. For example: +Associated type constructors can be bounded, just like associated types can be: ```rust -trait Collection { - type Iter<'a> where for<'a> Self::Iter<'a>: Iterator; - type IterMut<'a> where for<'a> Self::IterMut<'a>: Iterator; - type IntoIter: Iterator; +trait Iterable { + type Item<'a>; + type Iter<'a>: Iterator>; + + fn iter<'a>(&'a self) -> Self::Iter<'a>; } ``` -A `where` clause is used to avoid the impression that this is providing a -bound on the constructor itself. Note the contrast to `IntoIter`, which is -not a type constructor. Also note that this involves an extension to HRTB, -which is discussed later in this RFC. - -As a last note, these `where` clauses do not need to involve HRTB, but can -instead apply type/lifetime parameters or concrete types/lifetimes that are -in scope to the type constructor, as in: +This bound is applied to the "output" of the type constructor, and the parameter +is treated as a higher rank parameter. That is, the above bound is roughly +equivalent to adding this bound to the trait: ```rust -trait Foo { - type Bar where Self::Bar: Display; - type Baz<'a> where Self::Baz<'static>: Send; -} +for<'a> Self::Iter<'a>: Iterator> ``` -### Assigning an associated type constructor +Currently, this RFC only proposes adding associated type constructor of **lifetime** +arguments, but it is intended to be extended to type arguments once higher rank +type parameters are included. Assigning associated type constructors in impls is very similar to the syntax for assigning associated types: @@ -170,25 +157,12 @@ impl StreamingIterator for StreamIterMut { } ``` -Note that this example makes use of partial application (see the later section -on partial application for more information about this feature). The parameter -to this argument is quite clear, because it is the argument associated with -the type constructor. If there were multiple lifetimes involved, it would still -be unambiguous which was being applied and which isn't, for example: - -```rust -impl<'a> StreamingIterator for FooStreamIter<'a> { - type Item<'b> = &'b mut [Foo<'a>]; -} -``` - ### Using an associated type constructor to construct a type Once a trait has an associated type constructor, it can be applied to any -type/lifetime parameters or concrete types/lifetimes that are in scope. This -can be done both inside the body of the trait and outside of it, using syntax -which is analogous to the syntax for using associated types. Here are some -examples: +parameters or concrete term that are in scope. This can be done both inside the +body of the trait and outside of it, using syntax which is analogous to the +syntax for using associated types. Here are some examples: ```rust trait StreamingIterator { @@ -204,12 +178,11 @@ struct Foo { ``` Associated type constructors can also be used to construct other type -constructors through partial application (see the later section on partial -application for more information about this feature). +constructors: ```rust trait Foo { - type Bar<'a, T>; + type Bar<'a, 'b>; } trait Baz { @@ -217,7 +190,7 @@ trait Baz { } impl Baz for T where T: Foo { - type Quux<'a> = ::Bar<'a, usize>; + type Quux<'a> = ::Bar<'a, 'static>; } ``` @@ -244,117 +217,114 @@ fn foo StreamingIterator=&'a [i32]>>(iter: T) { ... } fn foo(iter: T) where T: StreamingIterator, for<'a> T::Item<'a>: Display { ... } ``` -See the section on extending HRTBs for more information about that aspect of -this feature. - This RFC does not propose allowing any sort of bound by the type constructor itself, whether an equality bound or a trait bound (trait bounds of course are -also impossible). That is, one can do the former but not the latter here: +also impossible). + +#### `let` introduction of parameters in bound + +The `for` syntax for HRTB is widely considered inaccessible and difficult to learn. +The problem here is not that the underlying concept of a higher rank parameter is +particularly challenging, but that the syntax introduces too much jargony syntax +which distracts from the underlying idea. + +This RFC proposes adding a new syntax for higher rank parameters which appear in the +type position of the bound. A user can simply introduce a new parameter with the `let` +keyword: ```rust -// Valid -fn foo Foo=Vec>>(x: T) { ... } +where T: Iterable, T::Item: &'a str +//equivalent to +where T: Iterable, for<'a> T::Item<'a>: &'a str +``` + +The variable introduced by the `let` is scoped only to this bound. Shadowing existing +type variables in scope is not permitted by a `let`, just as it is not permitted with +the existing `for` syntax. The `let` keyword is necessary to make it unambiguous that +the user intends to introduce a new variable here. -// Invalid -fn foo>(x: T) { ... } +The `let` syntax is valid for any type constructor being bound, including those which +are not associated items. As an arbitrary example: + +``` +where vec::Iter: ExactSizeIterator ``` -HRTBs allow us to express the same bounds without adding quite as radical a -new feature as adding bounds by equality of type constructors. - -## Partial Application - -In order for this feature to be useful, we will have to allow for type -constructors to partially applied. Many languages with higher-kinded -polymorphism use currying as an alternative to partial application. Rust does -not have currying at the level of expressions, and currying would not be -sufficient to enable the use cases that exist for type constructors, so this -RFC does not propose using currying for higher-kinded polymorphism. - -As an example, the reference operator has the kind `lifetime, type -> type`, -taking both a lifetime and a type to produce a new type. With currying, the two -parameters to the reference operator would have to have a defined order, and it -would be possible to partially apply only one of the parameters to the -reference operator. That is, if it were `lifetime -> type -> type`, one could -apply it to a lifetime to produce a `type -> type` operator, but one could not -apply it to a type to produce a `lifetime -> type` operator. If it were defined -as `type -> lifetime -> type`, it would be restricted in the opposite way. -Because Rust makes use of two base kinds, currying would severely restrict the -forms of abstraction enabled by Rust. - -Instead, when defining an associated type constructor, an anonymous type -constructor can be constructed from a type constructor with more parameters by -applying any of the parameters to that type constructor. The syntax discussed -below makes it unambiguous and easy to see which parameters remain undetermined -at the point of assigning the associated type constructor to a concrete type -constructor. - -When used in a type position, of course, all of the parameters to an associated -type constructor must have been applied to concrete types or type parameters that -are in scope, so that it can be evaluated to a proper type. - -## Extending HRTBs - -Providing bounds on the types constructed by associated type constructors -requires heavy use of HRTBs, or higher-ranked trait bounds. This exists in Rust -today, but in a limited form, and it is an obscure feature primarily used in -the background to make function traits behave as expected. - -In brief, a higher-ranked trait bound is one in which a type or lifetime -parameter is introduced only for the scope of that trait bound. A classic -example of how this can be useful is in the contrast between these two -functions: +Hypothetically, the `let` syntax could be expanded to positions outside of bounds, but +this RFC proposes no such extension. + +## Restrictions on ATCs + +In order to be forward compatible with higher order type constructors - which is commonly +called "full higher kinded types" - this RFC imposes certain restrictions on the kinds of +constructors which can be used as associated items. Background on this reasoning can be +found here: http://smallcultfollowing.com/babysteps/blog/2016/11/09/associated-type-constructors-part-4-unifying-atc-and-hkt/ + +The restriction is simple: Each lifetime argument of the constructor must be applied, in +order, and must be the left-most arguments of the type constructor. So all of these are +valid: ```rust -fn foo1(x: T, id: F) -> T where F: Fn(T) -> T { - id(x) +impl Trait for Type { + type Foo<'a> = &'a u32; + type Bar<'a> = SomeType<'a, 'static>; + type Baz<'a, 'b> = SomeType<'a, 'b>; + type Quux<'a> = Self::Foo<'a>; } +``` + +But these are not valid: -fn foo2(x: T, id: F) -> T where F: for Fn(X) -> X { - id(x) +```rust +impl Trait for Type { + type Foo<'a> = String; // ERROR! Argument never used. + type Bar<'a> = SomeType<'static, 'a>; // ERROR! Argument must be left-most. + type Baz<'a, 'b> = SomeType<'b, 'a>; // ERROR! Arguments are used in wrong order. + type Quux<'a> = (&'a i32, &'a i32); // ERROR! Argument is used more than once. } +``` -// Valid (evaluates to 4) -foo1::(2, |x| x + x) +All of these restrictions can be avoided (unpleasantly) with newtypes and phantomdata: -// Invalid (type error) -foo2::(2, |x| x + x) +```rust +struct ValidFoo<'a>(String, PhantomData<&'a ()>); +struct ValidBar<'a>(SomeType<'static, 'a>); +struct ValidBaz<'a, 'b>(SomeType<'a, 'b>); +struct Quux<'a>(&'a i32, &'a i32); ``` -In the second function, we _guarantee_ that the `id` argument is the identity -function (ignoring side effects), because it must be a valid function of `X` -to `X` for _all_ `X`, whereas the first can be specialized to only be a valid -function for the type `T`, in this case `i32`. +If this feature is extended to type arguments, the restriction remains the same within each +kind - so the same restriction with "lifetime" replaced with "type" is also added, but they do +not intermingle. + +## Future extensions + +The most immediate future extension to this feature is extending it to type arguments. + +For example: -Higher-ranked trait bounds have several other use cases. Currently, Rust uses -them to declare that arguments with different lifetimes can be passed to -function types, by requiring that that function be valid for all lifetimes, -rather than just for some single lifetime parameter. +```rust +trait Foo { + type Bar; +} +``` -In order to bound associated type constructors, we use higher-ranked types to -require that the type constructor constructs type which meet some bound. This -can be done both in the declaration and when bounding a type parameter by a -trait, and can be both a trait bound and a type equality bound. Here are -examples in code, with their meanings written out: +This sort of extension would enable to this feature to encode all forms of higher kinded +polymorphism, with some boilerplate, using the "family" pattern: ```rust -trait Sequence { - // For every lifetime, this constructor applied to that lifetime must - // produce a type which is an iterator of references of that lifetime - type Iter<'a> where for<'a> Iter<'a>: Iterator; +trait PointerFamily { + type Pointer: Deref; + fn new(value: T) -> Self::Poiner; } -// For every lifetime, the associated type constructor Item applied to -// that lifetime produces a reference of that lifetime to a slice of bytes. -struct Foo where T: for<'a> StreamingIterator { - ... +struct Foo { + bar: P::Pointer, } ``` -Enabling this requires extending HRTBs to support type parameters as well as -lifetime parameters. This would also imply that HRTBs could introduce type -parameters that themselves have bounds. The syntax for this is left to another -RFC. +Beyond this, this feature is intended to be compatible with extensions to "full HKT" +in the future. ## Benefits of implementing only this feature before other higher-kinded polymorphisms @@ -457,22 +427,16 @@ only allows for some of the types that associated type constructors can express, and is in generally a hacky attempt to work around the limitation rather than an equivalent alternative. -## Only add associated type constructors whose arguments are lifetimes +## Do not impose restrictions on associated type constructors -If associated type constructors could only take lifetime arguments, much of the -work extending HRTBs would not be necessary. Associated type constructors with -lifetime parameters only covers the primary known use cases for this feature. -Though it is inelegant to treat lifetime parameters differently from type -parameters here, at least as an implementation strategy it may make sense to -first implement this feature with lifetime parameters, and later extend it to -type parameters as well. +The restrictions imposed on this feature are only to be forward compatible +with other forms of higher kinded polymorphism. If we decided that we didn't +want to include those features ever, or that we were fine with those features +being totally disjoint from this one, we could not include those restrictions +in this RFC. # Unresolved questions [unresolved]: #unresolved-questions This design does not resolve the question of introducing more advanced forms of -higher-kinded polymorphism. This document does not describe the details of -implementing this RFC in terms of rustc's current typeck, because the author -is not familiar with that code. This document is certainly inadequate in its -description of this feature, most likely in relation to partial application, -because of the author's ignorance and personal defects. +higher-kinded polymorphism. From 28db2a5f58ed61dc6f5c6153a45a0e3fdda5a599 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Tue, 13 Jun 2017 09:46:52 -0700 Subject: [PATCH 04/10] No longer impose restrictions on ATCs. --- 0000-friends_in_high_kindednesses.md | 96 +++++++++++++--------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index 21bcb0fbe97..5a26aead5f3 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -15,7 +15,6 @@ system compared to other forms of higher-kinded polymorphism, and is forward compatible with more complex forms of higher-kinded polymorphism that may be introduced in the future. - # Motivation [motivation]: #motivation @@ -253,50 +252,6 @@ where vec::Iter: ExactSizeIterator Hypothetically, the `let` syntax could be expanded to positions outside of bounds, but this RFC proposes no such extension. -## Restrictions on ATCs - -In order to be forward compatible with higher order type constructors - which is commonly -called "full higher kinded types" - this RFC imposes certain restrictions on the kinds of -constructors which can be used as associated items. Background on this reasoning can be -found here: http://smallcultfollowing.com/babysteps/blog/2016/11/09/associated-type-constructors-part-4-unifying-atc-and-hkt/ - -The restriction is simple: Each lifetime argument of the constructor must be applied, in -order, and must be the left-most arguments of the type constructor. So all of these are -valid: - -```rust -impl Trait for Type { - type Foo<'a> = &'a u32; - type Bar<'a> = SomeType<'a, 'static>; - type Baz<'a, 'b> = SomeType<'a, 'b>; - type Quux<'a> = Self::Foo<'a>; -} -``` - -But these are not valid: - -```rust -impl Trait for Type { - type Foo<'a> = String; // ERROR! Argument never used. - type Bar<'a> = SomeType<'static, 'a>; // ERROR! Argument must be left-most. - type Baz<'a, 'b> = SomeType<'b, 'a>; // ERROR! Arguments are used in wrong order. - type Quux<'a> = (&'a i32, &'a i32); // ERROR! Argument is used more than once. -} -``` - -All of these restrictions can be avoided (unpleasantly) with newtypes and phantomdata: - -```rust -struct ValidFoo<'a>(String, PhantomData<&'a ()>); -struct ValidBar<'a>(SomeType<'static, 'a>); -struct ValidBaz<'a, 'b>(SomeType<'a, 'b>); -struct Quux<'a>(&'a i32, &'a i32); -``` - -If this feature is extended to type arguments, the restriction remains the same within each -kind - so the same restriction with "lifetime" replaced with "type" is also added, but they do -not intermingle. - ## Future extensions The most immediate future extension to this feature is extending it to type arguments. @@ -427,13 +382,52 @@ only allows for some of the types that associated type constructors can express, and is in generally a hacky attempt to work around the limitation rather than an equivalent alternative. -## Do not impose restrictions on associated type constructors +## Impose restrictions on ATCs + +What is often called "full higher kinded polymorphism" is allowing the use of +type constructors as input parameters to other type constructors - higher order +type constructors, in other words. Without any restrictions, multiparameter +higher order type constructors present serious problems for type inference. + +For example, if you are attempting to infer types, and you know you have a +constructor of the form `type, type -> Result<(), io::Error>`, without any +restrictions it is difficult to determine if this constructor is +`(), io::Error -> Result<(), io::Error>` or `io::Error, () -> Result<(), io::Error>`. + +Because of this, languages with first class higher kinded polymorphism tend to +impose restrictions on these higher kinded terms, such as Haskell's currying +rules. + +If Rust were to adopt higher order type constructors, it would need to impose +similar restrictions on the kinds of type constructors they can receive. But +associated type constructors, being a kind of alias, inherently mask the actual +structure of the concrete type constructor. In other words, if we want to be +able to use ATCs as arguments to higher order type constructors, we would need +to impose those restrictions on *all* ATCs. + +We have a list of restrictions we believe are necessary and sufficient; more +background can be found in [this blog post](http://smallcultfollowing.com/babysteps/blog/2016/11/09/associated-type-constructors-part-4-unifying-atc-and-hkt/) +by nmatsakis: + +* Each argument to the ATC must be applied +* They must be applied in the same order they appear in the ATC +* They must be applied exactly once +* They must be the left-most arguments of the constructor + +These restrictions are quite constrictive; there are several applications of +ATCs that we already know about that would be frustrated by this, such as the +definition of `Iterable` for `HashMap` (for which the item `(&'a K, &'a V)`, +applying the lifetime twice). + +For this reason we have decided **not** to apply these restrictions to all +ATCs. This will mean that if higher order type constructors are ever added to +the language, they will not be able to take an abstract ATC as an argument. +However, this can be maneuvered around using newtypes which do meet the +restrictions, for example: -The restrictions imposed on this feature are only to be forward compatible -with other forms of higher kinded polymorphism. If we decided that we didn't -want to include those features ever, or that we were fine with those features -being totally disjoint from this one, we could not include those restrictions -in this RFC. +```rust +struct IterItem<'a, I: Iterable>(I::Item<'a>); +``` # Unresolved questions [unresolved]: #unresolved-questions From e83709976babb6325e2060a54f1dd1ba91698876 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Tue, 13 Jun 2017 09:59:59 -0700 Subject: [PATCH 05/10] Add a how we teach this section. --- 0000-friends_in_high_kindednesses.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index 5a26aead5f3..489126e7610 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -309,6 +309,29 @@ syntax is that many users will be able to use types which have assocaited type constructors without even being aware that this has something to do with a type system feature called higher-kindedness. +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +This RFC uses the terminology "associated type constructor," which has become +the standard way to talk about this feature in the Rust community. This is not +a very accessible framing of this concept; in particular the term "type +constructor" is an obscure piece of jargon from type theory which most users +cannot be expected to be familiar with. + +Upon accepting this RFC, we should begin (with haste) refering to this concept +as simply "generic associated types." Today, associated types cannot be +generic; after this RFC, this will be possible. Rather than teaching this as +a separate feature, it will be taught as an advanced use case for associated +types. + +Patterns like "family traits" should also be taught in some way, possible in +the book or possibly just through supplemental forms of documentation like +blog posts. + +This will also likely increase the frequency with which users have to employ +higher rank trait bounds; we will want to put additional effort into teaching +and making teachable HRTBs. + # Drawbacks [drawbacks]: #drawbacks From 4d8b2f3e797ef33a43d5b673486438c32ea06ff7 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Tue, 13 Jun 2017 10:02:19 -0700 Subject: [PATCH 06/10] Fix typo. --- 0000-friends_in_high_kindednesses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index 489126e7610..7441e5e0fc7 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -270,7 +270,7 @@ polymorphism, with some boilerplate, using the "family" pattern: ```rust trait PointerFamily { type Pointer: Deref; - fn new(value: T) -> Self::Poiner; + fn new(value: T) -> Self::Pointer; } struct Foo { From 49ec778d483577915d8aff4b53930c76bb0807e1 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Tue, 13 Jun 2017 11:04:55 -0700 Subject: [PATCH 07/10] Make type arguments part of the RFC. --- 0000-friends_in_high_kindednesses.md | 73 +++++++++++----------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index 7441e5e0fc7..d3b0b8b5281 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -142,10 +142,6 @@ equivalent to adding this bound to the trait: for<'a> Self::Iter<'a>: Iterator> ``` -Currently, this RFC only proposes adding associated type constructor of **lifetime** -arguments, but it is intended to be extended to type arguments once higher rank -type parameters are included. - Assigning associated type constructors in impls is very similar to the syntax for assigning associated types: @@ -220,43 +216,10 @@ This RFC does not propose allowing any sort of bound by the type constructor itself, whether an equality bound or a trait bound (trait bounds of course are also impossible). -#### `let` introduction of parameters in bound - -The `for` syntax for HRTB is widely considered inaccessible and difficult to learn. -The problem here is not that the underlying concept of a higher rank parameter is -particularly challenging, but that the syntax introduces too much jargony syntax -which distracts from the underlying idea. - -This RFC proposes adding a new syntax for higher rank parameters which appear in the -type position of the bound. A user can simply introduce a new parameter with the `let` -keyword: - -```rust -where T: Iterable, T::Item: &'a str -//equivalent to -where T: Iterable, for<'a> T::Item<'a>: &'a str -``` - -The variable introduced by the `let` is scoped only to this bound. Shadowing existing -type variables in scope is not permitted by a `let`, just as it is not permitted with -the existing `for` syntax. The `let` keyword is necessary to make it unambiguous that -the user intends to introduce a new variable here. - -The `let` syntax is valid for any type constructor being bound, including those which -are not associated items. As an arbitrary example: - -``` -where vec::Iter: ExactSizeIterator -``` - -Hypothetically, the `let` syntax could be expanded to positions outside of bounds, but -this RFC proposes no such extension. - -## Future extensions - -The most immediate future extension to this feature is extending it to type arguments. +## Associated type constructors of type arguments -For example: +All of the examples in this RFC have focused on associated type constructors of +lifetime arguments, however, this RFC proposes adding ATCs of types as well: ```rust trait Foo { @@ -264,8 +227,13 @@ trait Foo { } ``` -This sort of extension would enable to this feature to encode all forms of higher kinded -polymorphism, with some boilerplate, using the "family" pattern: +This RFC does **not** propose extending HRTBs to take type arguments, which +makes these less expressive than they could be. Such an extension is desired, +but out of scope for this RFC. + +Type arguments can be used to encode other forms of higher kinded polymorphism +using the "family" pattern. For example, Using the `PointerFamily` trait, you +can abstract over Arc and Rc: ```rust trait PointerFamily { @@ -273,14 +241,29 @@ trait PointerFamily { fn new(value: T) -> Self::Pointer; } +struct ArcFamily; + +impl PointerFamily for ArcFamily { + type Pointer = Arc; + fn new(value: T) -> Self::Pointer { + Arc::new(value) + } +} + +struct RcFamily; + +impl PointerFamily for RcFamily { + type Pointer = Rc; + fn new(value: T) -> Self::Pointer { + Rc::new(value) + } +} + struct Foo { bar: P::Pointer, } ``` -Beyond this, this feature is intended to be compatible with extensions to "full HKT" -in the future. - ## Benefits of implementing only this feature before other higher-kinded polymorphisms This feature is not full-blown higher-kinded polymorphism, and does not allow From 7816f4f26e6fecc461026b7ef8ef0496fcbe198e Mon Sep 17 00:00:00 2001 From: Without Boats Date: Fri, 1 Sep 2017 11:13:33 -0700 Subject: [PATCH 08/10] Fix typo --- 0000-friends_in_high_kindednesses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index d3b0b8b5281..48fe17e76ca 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -128,7 +128,7 @@ Associated type constructors can be bounded, just like associated types can be: ```rust trait Iterable { type Item<'a>; - type Iter<'a>: Iterator>; + type Iter<'a>: Iterator>; fn iter<'a>(&'a self) -> Self::Iter<'a>; } From 7fccc9f1b6359a5f194b1046a105a3e8a39c2489 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sat, 2 Sep 2017 00:12:43 -0700 Subject: [PATCH 09/10] Add stuff about bounds on ATCs. --- 0000-friends_in_high_kindednesses.md | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/0000-friends_in_high_kindednesses.md b/0000-friends_in_high_kindednesses.md index 48fe17e76ca..6420cab91e1 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/0000-friends_in_high_kindednesses.md @@ -264,6 +264,47 @@ struct Foo { } ``` +## Evaluating bounds and where clauses + +### Bounds on associated type constructors + +Bounds on associated type constructors are treated as higher rank bounds on the +trait itself. This makes their behavior consistent with the behavior of bounds +on regular associated types. For example: + +```rust +trait Foo { + type Assoc<'a>: Trait<'a>; +} +``` + +Is equivalent to: + +```rust +trait Foo where for<'a> Self::Assoc<'a>: Trait<'a> { + type Assoc<'a>; +} +``` + +### `where` clauses on associated types + +In contrast, where clauses on associated types introduce constraints which must +be proven each time the associated type is used. For example: + +```rust +trait Foo { + type Assoc where Self: Sized; +} +``` + +Each invokation of `::Assoc` will need to prove `T: Sized`, as +opposed to the impl needing to prove the bound as in other cases. + +(@nikomatsakis believes that where clauses will be needed on associated type +constructors specifically to handle lifetime well formedness in some cases. +The exact details are left out of this RFC because they will emerge more fully +during implementation.) + ## Benefits of implementing only this feature before other higher-kinded polymorphisms This feature is not full-blown higher-kinded polymorphism, and does not allow From e14b17575c9b84063332cca2467a18d1e47008cb Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sat, 2 Sep 2017 00:16:28 -0700 Subject: [PATCH 10/10] RFC 1598 is Generic Associated Types. --- .../1598-generic_associated_types.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) rename 0000-friends_in_high_kindednesses.md => text/1598-generic_associated_types.md (98%) diff --git a/0000-friends_in_high_kindednesses.md b/text/1598-generic_associated_types.md similarity index 98% rename from 0000-friends_in_high_kindednesses.md rename to text/1598-generic_associated_types.md index 6420cab91e1..0f7cf71f8f2 100644 --- a/0000-friends_in_high_kindednesses.md +++ b/text/1598-generic_associated_types.md @@ -1,7 +1,7 @@ -- Feature Name: associated_type_constructors +- Feature Name: generic_associated_types - Start Date: 2016-04-29 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: [rust-lang/rfcs#1598](https://github.com/rust-lang/rfcs/pull/1598) +- Rust Issue: [rust-lang/rust#44265](https://github.com/rust-lang/rust/issues/44265) # Summary [summary]: #summary @@ -478,6 +478,3 @@ struct IterItem<'a, I: Iterable>(I::Item<'a>); # Unresolved questions [unresolved]: #unresolved-questions - -This design does not resolve the question of introducing more advanced forms of -higher-kinded polymorphism.