From b0bf8c5d5ba4310db3157dc13459ec1aa562476a Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 15:14:46 +0100 Subject: [PATCH 01/22] impl_trait_type_aliases --- text/0000-impl-trait-type-aliases.md | 311 +++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 text/0000-impl-trait-type-aliases.md diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md new file mode 100644 index 00000000000..aeeef1472c2 --- /dev/null +++ b/text/0000-impl-trait-type-aliases.md @@ -0,0 +1,311 @@ +- Feature Name: impl_trait_type_aliases +- Start Date: 2018-08-03 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow type aliases to use `impl Trait`, replacing the prototype `existential type` as a way to declare type aliases for opaque, uniquely inferred types. + +# Motivation +[motivation]: #motivation + +[RFC 2071](https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md) described a method to define opaque types satisfying certain bounds (described in RFC 2071 and elsewhere as *existential types*). It left open the question of what the precise concrete syntax for the feature should be, opting to use a placeholder syntax, `existential type`. Since then, a clearer picture has emerged as to how to rephrase `impl Trait` in terms of type inference, rather than existentially-quantified types, which also provides new motivation for a proposed concrete syntax making use of the existing and familiar syntax `impl Trait`. + +In essence, this RFC proposes that the syntax: + +```rust +type Foo = impl Bar; +``` + +be implemented with the same semantics as: + +```rust +existential type Foo: Bar; +``` + +and the existing placeholder removed. + +Furthermore, this RFC proposes a strategy by which the terminology surrounding `impl Trait` might be transitioned from existentially-type theoretic terminology to type inference terminology, reducing the cognitive complexity of the feature. + +## Semantic Justification +Currently, each occurrence `impl Trait` serves two complementary functional purposes. +1. It defines an opaque type `T` (that is, a new type whose precise identification is hidden) satisfying (trait and lifetime) bounds. +2. It infers the precise type for `T` (that must satisfy the bounds for `T`), based on its occurrences. + +Thus, the following code: + +```rust +fn foo() -> impl Bar { + // return some type implementing `Bar` +} +``` + +is functionally equivalent to: + +```rust +struct __foo_return(/* some inferred type (2) */); // (1) + +fn foo() -> __foo_return { + // return some type implementing `Bar` wrapped in `__foo_return` (3) +} +``` + +The generated type `__foo_return` is not exposed: it is automatically contructed from any valid type (as in `(3)`). + +RFC 2071 proposed a construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), in order to use such types in positions other than function arguments and return-types (for example, module-level). + +If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, this is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) + +However, if we justify the semantics of `impl Trait` solely using type inference (as in point 2 above, expounded below) then we can re-use `impl Trait` for the purpose of `existential type` consistently, leading to a more unified syntax and lower cognitive barrier to learning. + +Here, we define the syntax: + +```rust +type Foo = impl Bar; +``` + +to represent a type alias to a generated type: + +```rust +struct __Foo_alias(/* some inferred type */); +type Foo = __Foo_alias; +``` + +This is functionally identical to `existential type`, but remains consistent with `impl Trait` where the original generated type is technically still hidden (exposed through the type alias). + +### Aliasing argument-position and return-position `impl Trait` +Note that though the type alias above is not contextual, it can be used to alias any existing occurrence of `impl Trait` in return position, because the type it aliases is inferred. + +```rust +fn foo() -> impl Bar { + // return some type implementing `Bar` +} +``` + +can be replaced by: + +```rust +type Baz = impl Bar; + +fn foo() -> Baz { + // return some type implementing `Bar` +} +``` + +Using `Baz` in multiple locations constrains all occurrences of the inferred type to be the same, just as with `existential type`. + +To justify aliasing argument-position `impl Trait`, we describe the type inference as a form of generalised type inference over polymorphic types, known commonly as [ML-style "let polymorphism"](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Let-Polymorphism). Simply put, this allows the following code: + +```rust +fn foo(x: impl Bar) { + // ... +} +``` + +to be replaced by: + +```rust +type Baz = impl Bar; + +fn foo(x: Baz) { + // ... +} +``` + +Note that `Baz` must here be polymorphic over the type `A`, but we don't want to explicitly declare `Baz` polymorphic in its definition (it's essentially irrelevant to the type itself). We can justify this in theory by describing the type inference used for `impl Trait` to be a restricted form of let polymorphism; in practice this functions identically to `existential type`, requiring no additional implementation, and is subtle enough that most users will likely not even notice it. + +Notice that, apart from this subtlety, we can describe the type alias syntax using features that are already present in Rust, rather than introducing any new constructs. + +## Learnability Justification + +### Reduced technical and theoretic complexity +As a relatively recently stabilised feature, there is not significant (official) documentation on `impl Trait` so far. Apart from the various RFC threads and internal discussions, `impl Trait` [is described in a blog post](https://blog.rust-lang.org/2018/05/10/Rust-1.26.html) and in the [Rust 2018 edition guide](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/impl-trait.html). The edition guide primary describes `impl Trait` intuitively, in terms of use cases. It does however contain the following: + +> `impl Trait` in argument position are universal (universally quantified types). Meanwhile, `impl Trait` in return position are existentials (existentially quantified types). + +[This is incorrect](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html#confusion-2-return-position-impl-trait-vs-argument-position-impl-trait) (albeit subtly): in fact, the distinction between argument-position and return-position `impl Trait` is the scope of their existential quantifier. This (understandable) mistake is pervasive and it's not alone (the fact that those documenting the feature missed this is indicative of the issues). The problem stems from a poor understanding of what "existential types" are --- which is entirely unsurprising: existential types are a technical type theoretic concept that are not widely encountered outside type theory (unlike universally-quantified types, for instance). In discussions about existential types in Rust, these sorts of confusions are endemic. + +In any model that does not unify the meaning of `impl Trait` in various positions, these technical explanations are likely to arise, as they provide the original motivation for treating `impl Trait` nonhomogeneously. From this perspective, it is extremely valuable from documentation and explanatory angles to unify the uses of `impl Trait` so that these types of questions never even arise. Then we would have the ability to transition entirely away from the topic of existentially-quantified types. + +### Natural syntax +Having explained `impl Trait` solely in terms of type inference (or less formal equivalent explanations), the syntax proposed here is the only natural syntax. Indeed, while discussing the syntax here, many express surprise that this syntax has ever been under question (often from people who think of `impl Trait` from an intuition about the feature's behaviour, rather than thinking about the existential type perspective). + +The argument that is occasionally put forward: that this syntax makes type aliases (or their uses) somehow contextual, is also addressed by the above interpretation. Indeed, every use of an individual `impl Trait` type alias refers to the same type. + +The following section provides a documentation-style introductory explanation for `impl Trait` that justifies the type alias syntax proposed here. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +[Adapted from the [Rust 2018 edition guide](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/impl-trait.html#more-details).] + +`impl Trait` provides a way to specify unnamed concrete types with specific bounds. You can currently use it in three places (to be extended in future versions of Rust: see [the tracking issue](https://github.com/rust-lang/rust/issues/34511) for more details): +- Argument position +- Return position +- Type aliases + +```rust +trait Trait {} + +// Argument-position +fn foo(arg: impl Trait) { + // ... +} + +// Return-position +fn bar() -> impl Trait { + // ... +} + +// Type alias +type Baz = impl Trait; +``` + +## How does `impl Trait` work? +Whenever you write `impl Trait`, in any of the three places, you're saying that you have *some type* that implements `Trait`, but you don't want to expose any more information than that. The concrete type that implements `Trait` will be hidden, but you'll still be able to treat the type as if it implements `Type`: calling trait methods and so on. + +The compiler will infer the concrete type, but other code won't be able to make use of that fact. This is straightforward to describe, but it manifests a little differently depending on the place it's used, so let's take a look at some examples. + +## Argument-position +```rust +trait Trait {} + +fn foo(arg: impl Trait) { + // ... +} +``` + +Here, we're saying that `foo` takes an argument whose type implements `Trait`, but we're not saying exactly what it is. Thus, the caller can pass a value of any type, as long as it implements `Trait`. + +You may notice this sounds very like a generic type parameter. In fact, functionally, using `impl Trait` in argument position is almost identical to a generic type parameter. + +```rust +fn foo(arg: impl Trait) { + // ... +} + +// is almost the same as: + +fn foo(arg: T) { + // ... +} +``` + +The only difference is that you can't use turbo-fish syntax for the first definition (as turbo-fish syntax only works with explicit generic type parameters). Thus, it's worth being mindful that switching between `impl Trait` and generic type parameters can consistute a breaking change for users of your code. + +## Return-position +```rust +trait Trait {} + +impl Trait for i32 {} + +fn bar() -> impl Trait { + 5 +} +``` + +Using `impl Trait` as a return type is more useful, as it enables us to do things we weren't able to before. In this example, `bar` returns some type that's not specified: it just asserts that the type implements `Trait`. Inside the function, we can return any type that fits, but from the caller's perspective, all they know is that the type implements the trait. + +This is useful especially for two things: +- Hiding (potentially complex) implementation details +- Referring to types that were previously unnameable, such as closures + +[Here, we would also provide a more useful example, as in the [Rust 2018 edition guide](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/impl-trait.html#impl-trait-and-closures).] + +## Type alias +```rust +trait Trait {} + +type Baz = impl Trait; +``` + +`impl Trait` type aliases are useful for declaring types that are constrained by traits, but whose concrete type should be a hidden implementation detail. We can use it in place of argument-position or return-position `impl Trait` as in the previous examples. + +```rust +trait Trait {} + +type Baz = impl Trait; + +// The same as `fn foo(arg: impl Baz)` +fn foo(arg: Baz) { + // ... +} +``` + +```rust +trait Trait {} + +type Baz = impl Trait; + +// The same as `fn bar() -> impl Baz` +fn bar() -> Baz { + // ... +} +``` + +However, if we use `Baz` in multiple locations, we constrain the concrete type referred to by `Baz` to be the same, so we get a type that we know will be the same everywhere and will satisfy specific bounds, whose concrete type is hidden. This can be very useful in libraries where you want to hide implementation details. + +```rust +trait Trait {} + +type Baz = impl Trait; + +fn foo(x: Baz, y: Baz) { + // ... +} + +struct Foo { + a: Baz, + b: (Baz, Baz), +} +``` +In this example, the concrete type referred to by `Baz` is guaranteed to be the same wherever `Baz` occurs. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Since RFC 2071 was accepted, the initial implementation of `existential type` [has already been completed](https://github.com/rust-lang/rust/pull/52024). This RFC would simply replace the syntax of `existential type`, from: + +```rust +existential type Foo: Bar; +``` + +to: + +```rust +type Foo = impl Bar; +``` + +# Drawbacks +[drawbacks]: #drawbacks + +This feature has already been accepted under a placeholder syntax, so the only reason not to do this is if another syntax is chosen as a better choice, from an ergonomic and consistency perspective. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives +The justification for the type alias syntax proposed here comes down to two key motvations: +- Consistency +- Minimality + +Ideally a language should provide as small a surface area as possible. New keywords or constructs add to the cognitive complexity of a language, requiring users to look more concepts up or read larger guides to understand code they read and want to write. If it is possible to add new capabilities to the language that fit into the existing syntax and concepts, this generally increases cohesion. + +The syntax proposed here is a natural extension of the existing `impl Trait` syntax and it is felt that, should users encounter it after seeing argument-position and return-position `impl Trait`, its meaning will be immediately clear. On the other hand, new keywords or syntax will require the user to investigate further and provide more questions: +- "Why can't I use `impl Trait` here?" +- "What's the difference between `impl Trait` and X?" + +Using different syntax, and then trying to justify the differences between `impl Trait` and some new feature, seems likely to lead into conversations about existential types, which are almost always unhelpful for understanding. + +`type Foo = impl Bar;` has the additional benefit that it's easy to search for and can appear alongside documentation for other uses of `impl Trait`. + +The syntax `existential type` was intended to be a placeholder, so we need to pick a syntax eventually for this feature. Justification for why this is the best syntax, given the existing syntax in Rust, has been included throughout the RFC. + +The other alternatives commonly given are: +- `type Foo: impl Bar;`, which suffers from complete and confusing inconsistency with associated types. Although on the surface, they can appear similar to existential types, by virtue of being a declaration that "some type exists [that will be provided]", they are more closely related to type parameters (which also declare that "some type exists that will be provided"), though type parameters with [Haskell-style functional dependencies](https://wiki.haskell.org/Functional_dependencies). This is sure to lead to confusions as users wonder why two features with identical syntax turn out to behave so differently. +- Some other, new syntax for declaring a new type that acts in the same way as `existential type`. Though a new syntax would not be inconsistent, it would not be minimal, given that we can achieve the functionality using existing syntax (`impl Trait`). What's more, if the syntax proposed here were *not* added alongside this new syntax, this would lead to inconsistencies with `impl Trait`. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None. From 1082efdde798691903bd9fc849f6276ac59525a3 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 17:03:12 +0100 Subject: [PATCH 02/22] Correct some typos and make polymorphic type clearer --- text/0000-impl-trait-type-aliases.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index aeeef1472c2..07b5c513cda 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -99,7 +99,7 @@ Using `Baz` in multiple locations constrains all occurrences of the inferred typ To justify aliasing argument-position `impl Trait`, we describe the type inference as a form of generalised type inference over polymorphic types, known commonly as [ML-style "let polymorphism"](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Let-Polymorphism). Simply put, this allows the following code: ```rust -fn foo(x: impl Bar) { +fn foo(x: A, y: impl Bar) { // ... } ``` @@ -109,7 +109,7 @@ to be replaced by: ```rust type Baz = impl Bar; -fn foo(x: Baz) { +fn foo(x: A, y: Baz) { // ... } ``` @@ -302,7 +302,7 @@ Using different syntax, and then trying to justify the differences between `impl The syntax `existential type` was intended to be a placeholder, so we need to pick a syntax eventually for this feature. Justification for why this is the best syntax, given the existing syntax in Rust, has been included throughout the RFC. The other alternatives commonly given are: -- `type Foo: impl Bar;`, which suffers from complete and confusing inconsistency with associated types. Although on the surface, they can appear similar to existential types, by virtue of being a declaration that "some type exists [that will be provided]", they are more closely related to type parameters (which also declare that "some type exists that will be provided"), though type parameters with [Haskell-style functional dependencies](https://wiki.haskell.org/Functional_dependencies). This is sure to lead to confusions as users wonder why two features with identical syntax turn out to behave so differently. +- `type Foo: Bar;`, which suffers from complete and confusing inconsistency with associated types. Although on the surface, they can appear similar to existential types, by virtue of being a declaration that "some type exists [that will be provided]", they are more closely related to type parameters (which also declare that "some type exists that will be provided"), though type parameters with [Haskell-style functional dependencies](https://wiki.haskell.org/Functional_dependencies). This is sure to lead to confusions as users wonder why two features with identical syntax turn out to behave so differently. - Some other, new syntax for declaring a new type that acts in the same way as `existential type`. Though a new syntax would not be inconsistent, it would not be minimal, given that we can achieve the functionality using existing syntax (`impl Trait`). What's more, if the syntax proposed here were *not* added alongside this new syntax, this would lead to inconsistencies with `impl Trait`. # Unresolved questions From d6db251443e0dd395d9b7ca8810c69dad99d7059 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 17:10:25 +0100 Subject: [PATCH 03/22] Mention `impl Trait` type aliases in associated types --- text/0000-impl-trait-type-aliases.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 07b5c513cda..39db65632a6 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -263,6 +263,22 @@ struct Foo { ``` In this example, the concrete type referred to by `Baz` is guaranteed to be the same wherever `Baz` occurs. +Just like with any other type alias, we can use `impl Trait` to specify associated types for traits, as in the following example. + +```rust +trait Trait { + type Assoc; +} + +struct Foo {} + +impl Trait for Foo { + type Assoc = impl Debug; +} +``` + +Here, anything that makes use of `Foo` knows that `Foo::Assoc` implements `Debug`, but has no knowledge of its concrete type. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From c62791c7a0b29d6e52977c9a6648b2b29303c302 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 17:19:39 +0100 Subject: [PATCH 04/22] Forbid compound `impl Trait` type aliases --- text/0000-impl-trait-type-aliases.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 39db65632a6..f624ef8ea62 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -294,6 +294,18 @@ to: type Foo = impl Bar; ``` +The type alias syntax is more flexible than `existential type`, but for now we restrict the form to that equivalent to `existential type`. That means that, if `impl Trait` appears on the right-hand side of a type alias declaration, it must be the only type. The following compound type aliases, therefore, are initially forbidden: + +```rust +// error: compound type aliases containing `impl Bar` are disallowed +// hint: extract `impl Bar` from `A` +type A = (impl Bar, impl Bar); +type B = (u8, impl Bar); +type C = Vec; +``` + +This RFC does not prohibit the possibility that this rule could be relaxed in the future if it is found to be too restrictive. + # Drawbacks [drawbacks]: #drawbacks From c0a0fc6e229f22b1fb239e977e37b3bef36e4ce3 Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 17:54:55 +0100 Subject: [PATCH 05/22] Address referential transparency concerns --- text/0000-impl-trait-type-aliases.md | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index f624ef8ea62..5d57f22e7dd 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -132,7 +132,7 @@ In any model that does not unify the meaning of `impl Trait` in various position ### Natural syntax Having explained `impl Trait` solely in terms of type inference (or less formal equivalent explanations), the syntax proposed here is the only natural syntax. Indeed, while discussing the syntax here, many express surprise that this syntax has ever been under question (often from people who think of `impl Trait` from an intuition about the feature's behaviour, rather than thinking about the existential type perspective). -The argument that is occasionally put forward: that this syntax makes type aliases (or their uses) somehow contextual, is also addressed by the above interpretation. Indeed, every use of an individual `impl Trait` type alias refers to the same type. +The argument that is occasionally put forward: that this syntax makes type aliases (or their uses) somehow contextual, is also addressed by the above interpretation. Indeed, every use of an individual `impl Trait` type alias refers to the same type. This argument is [detailed and addressed further in **Drawbacks**](#drawbacks). The following section provides a documentation-style introductory explanation for `impl Trait` that justifies the type alias syntax proposed here. @@ -294,6 +294,9 @@ to: type Foo = impl Bar; ``` +In addition, when documenting `impl Trait`, explanations of the feature would avoid type theoretic terminology (specifically "existential types") and prefer type inference language (if any technical description is needed at all). + +## Restricting compound `impl Trait` trait aliases The type alias syntax is more flexible than `existential type`, but for now we restrict the form to that equivalent to `existential type`. That means that, if `impl Trait` appears on the right-hand side of a type alias declaration, it must be the only type. The following compound type aliases, therefore, are initially forbidden: ```rust @@ -311,6 +314,42 @@ This RFC does not prohibit the possibility that this rule could be relaxed in th This feature has already been accepted under a placeholder syntax, so the only reason not to do this is if another syntax is chosen as a better choice, from an ergonomic and consistency perspective. +There is one critique of the type alias syntax proposed here, which is frequently brought up in discussions, regarding referential transparency. + +Consider the following code: + +```rust +fn foo() -> impl Trait { /* ... */ } +fn bar() -> impl Trait { /* ... */ } +``` + +A user who has not come across `impl Trait` before might imagine that the return type of both functions is the same (as synactically, they are). However, because each occurrence of `impl Trait` defines a new type, the return types are potentially distinct. + +This is a problem inherent with `impl Trait` (and any other syntax that determines a type contextually) and thus `impl Trait` type aliases have the same caveat. + +A user unaware of the behaviour of `impl Trait` might try refactoring this example into the following: + +```rust +type SharedImplTrait = impl Trait; + +fn foo() -> SharedImplTrait { /* ... */ } +fn bar() -> SharedImplTrait { /* ... */ } +``` + +This evidently means something different to what the user intended, because here `SharedImplTrait` is inferred as a single type, shared with `foo` and `bar`. + +However, this problem is specifically with the behaviour of `impl Trait` and not with the type aliases, whose behaviour is not altered. Specifically note that, after this RFC, it is still true that for any type alias: + +```rust +type Alias = /* ... */; +``` + +all uses of `Alias` refer to the same unique type. The potential confusion is rather with whether all uses of `impl Trait` refer to the same unique type (which is, of course, false). + +This RFC suggests that users confused about `impl Trait` in argument or return position will be similarly confused about `impl Trait` in type aliases, and vice versa. + +Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives The justification for the type alias syntax proposed here comes down to two key motvations: From 3c257848be81f43645817bc08e600231d496ddff Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 3 Aug 2018 19:44:43 +0100 Subject: [PATCH 06/22] Rephrase sentence --- text/0000-impl-trait-type-aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 5d57f22e7dd..25842faead5 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -346,7 +346,7 @@ type Alias = /* ... */; all uses of `Alias` refer to the same unique type. The potential confusion is rather with whether all uses of `impl Trait` refer to the same unique type (which is, of course, false). -This RFC suggests that users confused about `impl Trait` in argument or return position will be similarly confused about `impl Trait` in type aliases, and vice versa. +It is likely that a misunderstanding of the nature of `impl Trait` in argument or return position will lead to similar confusion as to the role of `impl Trait` in type aliases, and vice versa. By clearly teaching the behaviour of `impl Trait`, we should be able to eliminate most of these conceptual difficulties. Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) From 5d1536988fb976ec27ac7cf7b99608c5a8e0001a Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 00:07:46 +0100 Subject: [PATCH 07/22] Clarify APIT inconsistency --- text/0000-impl-trait-type-aliases.md | 63 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 25842faead5..55a7eb8e7b0 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -96,27 +96,7 @@ fn foo() -> Baz { Using `Baz` in multiple locations constrains all occurrences of the inferred type to be the same, just as with `existential type`. -To justify aliasing argument-position `impl Trait`, we describe the type inference as a form of generalised type inference over polymorphic types, known commonly as [ML-style "let polymorphism"](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Let-Polymorphism). Simply put, this allows the following code: - -```rust -fn foo(x: A, y: impl Bar) { - // ... -} -``` - -to be replaced by: - -```rust -type Baz = impl Bar; - -fn foo(x: A, y: Baz) { - // ... -} -``` - -Note that `Baz` must here be polymorphic over the type `A`, but we don't want to explicitly declare `Baz` polymorphic in its definition (it's essentially irrelevant to the type itself). We can justify this in theory by describing the type inference used for `impl Trait` to be a restricted form of let polymorphism; in practice this functions identically to `existential type`, requiring no additional implementation, and is subtle enough that most users will likely not even notice it. - -Notice that, apart from this subtlety, we can describe the type alias syntax using features that are already present in Rust, rather than introducing any new constructs. +Notice that we can describe the type alias syntax using features that are already present in Rust, rather than introducing any new constructs. ## Learnability Justification @@ -221,47 +201,55 @@ trait Trait {} type Baz = impl Trait; ``` -`impl Trait` type aliases are useful for declaring types that are constrained by traits, but whose concrete type should be a hidden implementation detail. We can use it in place of argument-position or return-position `impl Trait` as in the previous examples. +`impl Trait` type aliases are useful for declaring types that are constrained by traits, but whose concrete type should be a hidden implementation detail. We can use it in place of return-position `impl Trait` as in the previous examples. ```rust trait Trait {} type Baz = impl Trait; -// The same as `fn foo(arg: impl Baz)` -fn foo(arg: Baz) { +// The same as `fn bar() -> impl Baz` +fn bar() -> Baz { // ... } ``` +However, if we use `Baz` in multiple locations, we constrain the concrete type referred to by `Baz` to be the same, so we get a type that we know will be the same everywhere and will satisfy specific bounds, whose concrete type is hidden. This can be very useful in libraries where you want to hide implementation details. + ```rust trait Trait {} type Baz = impl Trait; -// The same as `fn bar() -> impl Baz` -fn bar() -> Baz { +fn foo(x: Baz, y: Baz) { // ... } + +struct Foo { + a: Baz, + b: (Baz, Baz), +} ``` -However, if we use `Baz` in multiple locations, we constrain the concrete type referred to by `Baz` to be the same, so we get a type that we know will be the same everywhere and will satisfy specific bounds, whose concrete type is hidden. This can be very useful in libraries where you want to hide implementation details. +In this example, the concrete type referred to by `Baz` is guaranteed to be the same wherever `Baz` occurs. + +Note that using `Baz` as an argument type is *not* the same as argument-position `impl Trait`, as `Baz` refers to a unique type, whereas the concrete type for argument-position `impl Trait` is determined by the caller. ```rust trait Trait {} type Baz = impl Trait; -fn foo(x: Baz, y: Baz) { +fn foo(x: Baz) { // ... } -struct Foo { - a: Baz, - b: (Baz, Baz), +// is *not* the same as: + +fn foo(x: impl Trait) { + // ... } ``` -In this example, the concrete type referred to by `Baz` is guaranteed to be the same wherever `Baz` occurs. Just like with any other type alias, we can use `impl Trait` to specify associated types for traits, as in the following example. @@ -350,6 +338,17 @@ It is likely that a misunderstanding of the nature of `impl Trait` in argument o Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) +## Argument-position `impl Trait` +As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller, as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). Argument-position `impl Trait` makes use of a form of ML-style let polymorphism that is not present in the type aliases. + +Due to this inconsistency, it is impossible to define an `impl Trait` syntax that satisfies the following constraints: +- Argument-position `impl Trait` is consistent with return-position `impl Trait` +- Argument-position `impl Trait` is consistent with trait alias `impl Trait` +- Trait alias `impl Trait` is consistent with the existing notion of `existential type` +- The syntax for return-position `impl Trait` is consistent with the syntax for the existing notion of `existential type` + +Of these drawbacks, making argument-position `impl Trait` consistent with the other positions for `impl Trait` seems the most acceptable and intuitive. Using a syntax other than `impl Trait` for thsi feature would remove the one consistency we want to preserve: that is, that return-position `impl Trait` is the same notation as `existential type`. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives The justification for the type alias syntax proposed here comes down to two key motvations: From 70baad77629b8a4e3964d8da95b9e445888c9012 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 00:49:54 +0100 Subject: [PATCH 08/22] Explain let polymorphism better --- text/0000-impl-trait-type-aliases.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 55a7eb8e7b0..edc828691ad 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -75,7 +75,9 @@ type Foo = __Foo_alias; This is functionally identical to `existential type`, but remains consistent with `impl Trait` where the original generated type is technically still hidden (exposed through the type alias). -### Aliasing argument-position and return-position `impl Trait` +Note that, in order for the type inference to support argument-position `impl Trait`, which may be polymorphic (just like a generic parameter), the inference used here is actually a more expressive form of type inference similar to ML-style let polymorphism. Here, the inference of function types may result in additional generic parameters, specifically relating to the occurrences of argument-position `impl Trait`. + +### Aliasing `impl Trait` in function signatures Note that though the type alias above is not contextual, it can be used to alias any existing occurrence of `impl Trait` in return position, because the type it aliases is inferred. ```rust @@ -94,6 +96,8 @@ fn foo() -> Baz { } ``` +However, if the function is parameterised, it may be necessary to add explicit parameters to the type alias (due to the return-type being within the scope of the function's generic paramters, unlike the type alias). + Using `Baz` in multiple locations constrains all occurrences of the inferred type to be the same, just as with `existential type`. Notice that we can describe the type alias syntax using features that are already present in Rust, rather than introducing any new constructs. @@ -339,15 +343,7 @@ It is likely that a misunderstanding of the nature of `impl Trait` in argument o Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) ## Argument-position `impl Trait` -As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller, as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). Argument-position `impl Trait` makes use of a form of ML-style let polymorphism that is not present in the type aliases. - -Due to this inconsistency, it is impossible to define an `impl Trait` syntax that satisfies the following constraints: -- Argument-position `impl Trait` is consistent with return-position `impl Trait` -- Argument-position `impl Trait` is consistent with trait alias `impl Trait` -- Trait alias `impl Trait` is consistent with the existing notion of `existential type` -- The syntax for return-position `impl Trait` is consistent with the syntax for the existing notion of `existential type` - -Of these drawbacks, making argument-position `impl Trait` consistent with the other positions for `impl Trait` seems the most acceptable and intuitive. Using a syntax other than `impl Trait` for thsi feature would remove the one consistency we want to preserve: that is, that return-position `impl Trait` is the same notation as `existential type`. +As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller, as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From f45db44f6fff7f0982da4100f9732ddf66d26699 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 01:02:04 +0100 Subject: [PATCH 09/22] Make some minor adjustments --- text/0000-impl-trait-type-aliases.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index edc828691ad..6da445b9b03 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -54,9 +54,11 @@ fn foo() -> __foo_return { The generated type `__foo_return` is not exposed: it is automatically contructed from any valid type (as in `(3)`). +Note that, in order for the type inference to support argument-position `impl Trait`, which may be polymorphic (just like a generic parameter), the inference used here is actually a more expressive form of type inference similar to ML-style let polymorphism. Here, the inference of function types may result in additional generic parameters, specifically relating to the occurrences of argument-position `impl Trait`. + RFC 2071 proposed a construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), in order to use such types in positions other than function arguments and return-types (for example, module-level). -If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, this is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) +If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, defining a new construct is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) However, if we justify the semantics of `impl Trait` solely using type inference (as in point 2 above, expounded below) then we can re-use `impl Trait` for the purpose of `existential type` consistently, leading to a more unified syntax and lower cognitive barrier to learning. @@ -75,8 +77,6 @@ type Foo = __Foo_alias; This is functionally identical to `existential type`, but remains consistent with `impl Trait` where the original generated type is technically still hidden (exposed through the type alias). -Note that, in order for the type inference to support argument-position `impl Trait`, which may be polymorphic (just like a generic parameter), the inference used here is actually a more expressive form of type inference similar to ML-style let polymorphism. Here, the inference of function types may result in additional generic parameters, specifically relating to the occurrences of argument-position `impl Trait`. - ### Aliasing `impl Trait` in function signatures Note that though the type alias above is not contextual, it can be used to alias any existing occurrence of `impl Trait` in return position, because the type it aliases is inferred. @@ -109,7 +109,7 @@ As a relatively recently stabilised feature, there is not significant (official) > `impl Trait` in argument position are universal (universally quantified types). Meanwhile, `impl Trait` in return position are existentials (existentially quantified types). -[This is incorrect](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html#confusion-2-return-position-impl-trait-vs-argument-position-impl-trait) (albeit subtly): in fact, the distinction between argument-position and return-position `impl Trait` is the scope of their existential quantifier. This (understandable) mistake is pervasive and it's not alone (the fact that those documenting the feature missed this is indicative of the issues). The problem stems from a poor understanding of what "existential types" are --- which is entirely unsurprising: existential types are a technical type theoretic concept that are not widely encountered outside type theory (unlike universally-quantified types, for instance). In discussions about existential types in Rust, these sorts of confusions are endemic. +[This is incorrect](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html#confusion-2-return-position-impl-trait-vs-argument-position-impl-trait) (albeit subtly): in fact, the distinction between argument-position and return-position `impl Trait` is the scope of their existential quantifier. This (understandable) mistake is pervasive and it's not alone (the fact that those documenting the feature missed this is indicative of the issues surrounding this mental model). The problem stems from a poor understanding of what "existential types" are — which is entirely unsurprising: existential types are a technical type theoretic concept that are not widely encountered outside type theory (unlike universally-quantified types, for instance). In discussions about existential types in Rust, these sorts of confusions are endemic. In any model that does not unify the meaning of `impl Trait` in various positions, these technical explanations are likely to arise, as they provide the original motivation for treating `impl Trait` nonhomogeneously. From this perspective, it is extremely valuable from documentation and explanatory angles to unify the uses of `impl Trait` so that these types of questions never even arise. Then we would have the ability to transition entirely away from the topic of existentially-quantified types. @@ -343,7 +343,7 @@ It is likely that a misunderstanding of the nature of `impl Trait` in argument o Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) ## Argument-position `impl Trait` -As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller, as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). +As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 7fbc15992813743e50ce79f1dde7b1036d95ddb8 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 01:08:12 +0100 Subject: [PATCH 10/22] Stress new --- text/0000-impl-trait-type-aliases.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 6da445b9b03..4a3775dfd6a 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -56,9 +56,9 @@ The generated type `__foo_return` is not exposed: it is automatically contructed Note that, in order for the type inference to support argument-position `impl Trait`, which may be polymorphic (just like a generic parameter), the inference used here is actually a more expressive form of type inference similar to ML-style let polymorphism. Here, the inference of function types may result in additional generic parameters, specifically relating to the occurrences of argument-position `impl Trait`. -RFC 2071 proposed a construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), in order to use such types in positions other than function arguments and return-types (for example, module-level). +RFC 2071 proposed a new construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), in order to use such types in positions other than function arguments and return-types (for example, at the module level). -If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, defining a new construct is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) +If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, this new construct is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) However, if we justify the semantics of `impl Trait` solely using type inference (as in point 2 above, expounded below) then we can re-use `impl Trait` for the purpose of `existential type` consistently, leading to a more unified syntax and lower cognitive barrier to learning. From 21322a6df9e482f72e6a84f1ca1203c7605c5bdc Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 01:16:27 +0100 Subject: [PATCH 11/22] Delete APIT drawback paragraph --- text/0000-impl-trait-type-aliases.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 4a3775dfd6a..06e6f17fc3d 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -342,9 +342,6 @@ It is likely that a misunderstanding of the nature of `impl Trait` in argument o Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) -## Argument-position `impl Trait` -As described in the [Guide-level explanation](#guide-level-explanation), although we can freely replace an occurence of a return-position `impl Trait` with an `impl Trait` type alias, we cannot freely replace an occurrence of an argument-position `impl Trait`, as argument-position `impl Trait` may be polymorphic, determined by the caller as with a generic parameter. However, `impl Trait` type aliases are strictly monomorphic. Unfortunately this is an inherent restriction due to the inconsistency of argument-position `impl Trait` with return-position `impl Trait` (regarding the quantifier scope). - # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives The justification for the type alias syntax proposed here comes down to two key motvations: From 1b1051244b0591834c883697efe7062193b4f7d6 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 01:46:14 +0100 Subject: [PATCH 12/22] Clarify a use of "aliases" --- text/0000-impl-trait-type-aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 06e6f17fc3d..11a7d035a37 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -340,7 +340,7 @@ all uses of `Alias` refer to the same unique type. The potential confusion is ra It is likely that a misunderstanding of the nature of `impl Trait` in argument or return position will lead to similar confusion as to the role of `impl Trait` in type aliases, and vice versa. By clearly teaching the behaviour of `impl Trait`, we should be able to eliminate most of these conceptual difficulties. -Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about aliases. (What's more, examples in the reference will illustrate this clearly.) +Since we will teach `impl Trait` cohesively (that is, argument-position, return-position and type alias `impl Trait` at the same time), it is unlikely that users who understand `impl Trait` will be confused about `impl Trait` type aliases. (What's more, examples in the reference will illustrate this clearly.) # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 2cf0366f04f42c516e1cd6a40c83c3eba78ecc57 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 13:02:05 +0100 Subject: [PATCH 13/22] Add note about VBIT --- text/0000-impl-trait-type-aliases.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 11a7d035a37..8bb00b11337 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -271,6 +271,8 @@ impl Trait for Foo { Here, anything that makes use of `Foo` knows that `Foo::Assoc` implements `Debug`, but has no knowledge of its concrete type. +[Eventually, we would also describe the use of `impl Trait` in `let`, `const` and `static` bindings, but as they are as-yet unimplemented and function the same as return-type `impl Trait`, they haven't been included here.] + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 40768583a42ce0e888c83491f4d8c6125860f037 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 13:39:25 +0100 Subject: [PATCH 14/22] Add a defining use for Baz --- text/0000-impl-trait-type-aliases.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 8bb00b11337..b81ebaf3cf5 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -225,7 +225,15 @@ trait Trait {} type Baz = impl Trait; -fn foo(x: Baz, y: Baz) { +impl Trait for u8 {} + +fn foo() -> Baz { + let x: u8; + // ... + x +} + +fn bar(x: Baz, y: Baz) { // ... } From 956a51df228d99cfef795f08653dce89064ea067 Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 5 Aug 2018 14:18:33 +0100 Subject: [PATCH 15/22] Make some minor adjustments --- text/0000-impl-trait-type-aliases.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index b81ebaf3cf5..8375ed09c5a 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -25,13 +25,13 @@ be implemented with the same semantics as: existential type Foo: Bar; ``` -and the existing placeholder removed. +and that existing placeholder removed. Furthermore, this RFC proposes a strategy by which the terminology surrounding `impl Trait` might be transitioned from existentially-type theoretic terminology to type inference terminology, reducing the cognitive complexity of the feature. ## Semantic Justification Currently, each occurrence `impl Trait` serves two complementary functional purposes. -1. It defines an opaque type `T` (that is, a new type whose precise identification is hidden) satisfying (trait and lifetime) bounds. +1. It defines an opaque type `T` (that is, a new type whose precise identification is hidden) satisfying (trait) bounds. 2. It infers the precise type for `T` (that must satisfy the bounds for `T`), based on its occurrences. Thus, the following code: @@ -56,7 +56,7 @@ The generated type `__foo_return` is not exposed: it is automatically contructed Note that, in order for the type inference to support argument-position `impl Trait`, which may be polymorphic (just like a generic parameter), the inference used here is actually a more expressive form of type inference similar to ML-style let polymorphism. Here, the inference of function types may result in additional generic parameters, specifically relating to the occurrences of argument-position `impl Trait`. -RFC 2071 proposed a new construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), in order to use such types in positions other than function arguments and return-types (for example, at the module level). +RFC 2071 proposed a new construct for declaring types acting like `impl Trait`, but whose actual type was not hidden (i.e. a method to expose the `__foo_return` above), to use such types in positions other than function arguments and return-types (for example, at the module level). If the semantics of `impl Trait` are justified from the perspective of existentially-quantified types, this new construct is a sensible solution as re-using `impl Trait` for this purpose introduces additional inconsistency with the existential quantifier scopes. (See [here](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html) for more details on this point.) @@ -111,7 +111,7 @@ As a relatively recently stabilised feature, there is not significant (official) [This is incorrect](https://varkor.github.io/blog/2018/07/03/existential-types-in-rust.html#confusion-2-return-position-impl-trait-vs-argument-position-impl-trait) (albeit subtly): in fact, the distinction between argument-position and return-position `impl Trait` is the scope of their existential quantifier. This (understandable) mistake is pervasive and it's not alone (the fact that those documenting the feature missed this is indicative of the issues surrounding this mental model). The problem stems from a poor understanding of what "existential types" are — which is entirely unsurprising: existential types are a technical type theoretic concept that are not widely encountered outside type theory (unlike universally-quantified types, for instance). In discussions about existential types in Rust, these sorts of confusions are endemic. -In any model that does not unify the meaning of `impl Trait` in various positions, these technical explanations are likely to arise, as they provide the original motivation for treating `impl Trait` nonhomogeneously. From this perspective, it is extremely valuable from documentation and explanatory angles to unify the uses of `impl Trait` so that these types of questions never even arise. Then we would have the ability to transition entirely away from the topic of existentially-quantified types. +In any model that does not unify the meaning of `impl Trait` in various positions, these technical explanations are likely to arise, as they provide the original motivation for treating `impl Trait` nonhomogeneously. From this perspective, it is valuable from documentation and explanatory angles to unify the uses of `impl Trait` so that these types of questions never even arise. Then we would have the ability to transition entirely away from the topic of existentially-quantified types. ### Natural syntax Having explained `impl Trait` solely in terms of type inference (or less formal equivalent explanations), the syntax proposed here is the only natural syntax. Indeed, while discussing the syntax here, many express surprise that this syntax has ever been under question (often from people who think of `impl Trait` from an intuition about the feature's behaviour, rather than thinking about the existential type perspective). @@ -148,7 +148,7 @@ type Baz = impl Trait; ``` ## How does `impl Trait` work? -Whenever you write `impl Trait`, in any of the three places, you're saying that you have *some type* that implements `Trait`, but you don't want to expose any more information than that. The concrete type that implements `Trait` will be hidden, but you'll still be able to treat the type as if it implements `Type`: calling trait methods and so on. +Whenever you write `impl Trait`, in any of the three places, you're saying that you have *some type* that implements `Trait`, but you don't want to expose any more information than that. The concrete type that implements `Trait` will be hidden, but you'll still be able to treat the type as if it implements `Trait`: calling trait methods and so on. The compiler will infer the concrete type, but other code won't be able to make use of that fact. This is straightforward to describe, but it manifests a little differently depending on the place it's used, so let's take a look at some examples. @@ -218,7 +218,7 @@ fn bar() -> Baz { } ``` -However, if we use `Baz` in multiple locations, we constrain the concrete type referred to by `Baz` to be the same, so we get a type that we know will be the same everywhere and will satisfy specific bounds, whose concrete type is hidden. This can be very useful in libraries where you want to hide implementation details. +However, if we use `Baz` in multiple locations, we constrain the concrete type referred to by `Baz` to be the same, so we get a type that we know will be the same everywhere and will satisfy specific bounds, whose concrete type is hidden. This can be useful in libraries where you want to hide implementation details. ```rust trait Trait {} From 2b921f95df2cbadb40c908c2ef4d75f916ed82ee Mon Sep 17 00:00:00 2001 From: varkor Date: Fri, 17 Aug 2018 19:52:49 +0100 Subject: [PATCH 16/22] Add "removing the position restriction" as an unresolved question --- text/0000-impl-trait-type-aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 8375ed09c5a..d5736ccfa67 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -377,4 +377,4 @@ The other alternatives commonly given are: # Unresolved questions [unresolved-questions]: #unresolved-questions -None. +- The restriction of type alias `impl Trait` to the forms that are currently possible with `existential trait` is potentially unnecessary. Although this was proposed to simplify the RFC by only changing syntax, it could be a better choice to allow `impl Trait` anywhere in a type alias (by desugaring each occurrence into a separate inferred type). From 1691e6e4cecd219bb9505c237440b57728d42d7f Mon Sep 17 00:00:00 2001 From: varkor Date: Mon, 24 Sep 2018 20:37:31 +0100 Subject: [PATCH 17/22] Add examples containing generic parameters --- text/0000-impl-trait-type-aliases.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index d5736ccfa67..09ef0fcbf1c 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -298,6 +298,26 @@ type Foo = impl Bar; In addition, when documenting `impl Trait`, explanations of the feature would avoid type theoretic terminology (specifically "existential types") and prefer type inference language (if any technical description is needed at all). +`impl Trait` type aliases may contain generic parameters just like any other type alias. The type alias must contain the same type parameters as its concrete type, except those implicitly captured in the scope (see [RFC 2071](https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md) for details). + +```rust +// `impl Trait` type aliases may contain type parameters... +#[derive(Debug)] +struct DebugWrapper(T); + +type Foo = impl Debug; + +fn get_foo(x: T) -> Foo { DebugWrapper(x) } + +// ...and lifetime parameters (and so on). +#[derive(Debug)] +struct UnitRefWrapper<'a>(&'a ()); + +type Bar<'a> = impl Debug; + +fn get_bar<'a>(y: &'a ()) -> Bar<'a> { UnitRefWrapper(y) } +``` + ## Restricting compound `impl Trait` trait aliases The type alias syntax is more flexible than `existential type`, but for now we restrict the form to that equivalent to `existential type`. That means that, if `impl Trait` appears on the right-hand side of a type alias declaration, it must be the only type. The following compound type aliases, therefore, are initially forbidden: From b02be50f27e2adca0f2ce70f9942d151eaa77fe9 Mon Sep 17 00:00:00 2001 From: varkor Date: Thu, 28 Mar 2019 22:28:41 +0000 Subject: [PATCH 18/22] Mention associated types in the summary and motivation --- text/0000-impl-trait-type-aliases.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 09ef0fcbf1c..20edbb21ef1 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -Allow type aliases to use `impl Trait`, replacing the prototype `existential type` as a way to declare type aliases for opaque, uniquely inferred types. +Allow type aliases and associated types to use `impl Trait`, replacing the prototype `existential type` as a way to declare type aliases and associated types for opaque, uniquely inferred types. # Motivation [motivation]: #motivation @@ -25,7 +25,7 @@ be implemented with the same semantics as: existential type Foo: Bar; ``` -and that existing placeholder removed. +both as the syntax for type aliases and also for associated types, and that existing placeholder be removed. Furthermore, this RFC proposes a strategy by which the terminology surrounding `impl Trait` might be transitioned from existentially-type theoretic terminology to type inference terminology, reducing the cognitive complexity of the feature. From e7e26eac6880bd617cfc7a1576d0421a5558c725 Mon Sep 17 00:00:00 2001 From: varkor Date: Thu, 28 Mar 2019 22:31:49 +0000 Subject: [PATCH 19/22] Clarify multiple impl Trait semantics --- text/0000-impl-trait-type-aliases.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 20edbb21ef1..19b12cdbfba 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -398,3 +398,18 @@ The other alternatives commonly given are: [unresolved-questions]: #unresolved-questions - The restriction of type alias `impl Trait` to the forms that are currently possible with `existential trait` is potentially unnecessary. Although this was proposed to simplify the RFC by only changing syntax, it could be a better choice to allow `impl Trait` anywhere in a type alias (by desugaring each occurrence into a separate inferred type). + +In this case, each occurrence of `impl Trait` would be desugared to a new existential type. For example, +the following alias: + +```rust +type Foo = Arc>; +``` + +would be desugared to the equivalent of: + +```rust +existential type _0: Debug; +existential type _1: Iterator; +type Foo = Arc<_1>; +``` \ No newline at end of file From f81510c80164bd9a8330934a1a816114e6cd7394 Mon Sep 17 00:00:00 2001 From: varkor Date: Thu, 28 Mar 2019 22:48:46 +0000 Subject: [PATCH 20/22] Resolve multiple `impl Trait` question --- text/0000-impl-trait-type-aliases.md | 35 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index 19b12cdbfba..c24d54cbc0a 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -284,7 +284,7 @@ Here, anything that makes use of `Foo` knows that `Foo::Assoc` implements `Debug # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Since RFC 2071 was accepted, the initial implementation of `existential type` [has already been completed](https://github.com/rust-lang/rust/pull/52024). This RFC would simply replace the syntax of `existential type`, from: +Since RFC 2071 was accepted, the initial implementation of `existential type` [has already been completed](https://github.com/rust-lang/rust/pull/52024). This RFC would replace the syntax of `existential type`, from: ```rust existential type Foo: Bar; @@ -296,7 +296,21 @@ to: type Foo = impl Bar; ``` -In addition, when documenting `impl Trait`, explanations of the feature would avoid type theoretic terminology (specifically "existential types") and prefer type inference language (if any technical description is needed at all). +In addition, having multiple occurrences of `impl Trait` in a type alias or associated type is now permitted, where each occurrence is desugared into a separate inferred type. For example, the following alias: + +```rust +type Foo = Arc>; +``` + +would be desugared to the equivalent of: + +```rust +existential type _0: Debug; +existential type _1: Iterator; +type Foo = Arc<_1>; +``` + +Furthermore, when documenting `impl Trait`, explanations of the feature would avoid type theoretic terminology (specifically "existential types") and prefer type inference language (if any technical description is needed at all). `impl Trait` type aliases may contain generic parameters just like any other type alias. The type alias must contain the same type parameters as its concrete type, except those implicitly captured in the scope (see [RFC 2071](https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-existential-types.md) for details). @@ -397,19 +411,4 @@ The other alternatives commonly given are: # Unresolved questions [unresolved-questions]: #unresolved-questions -- The restriction of type alias `impl Trait` to the forms that are currently possible with `existential trait` is potentially unnecessary. Although this was proposed to simplify the RFC by only changing syntax, it could be a better choice to allow `impl Trait` anywhere in a type alias (by desugaring each occurrence into a separate inferred type). - -In this case, each occurrence of `impl Trait` would be desugared to a new existential type. For example, -the following alias: - -```rust -type Foo = Arc>; -``` - -would be desugared to the equivalent of: - -```rust -existential type _0: Debug; -existential type _1: Iterator; -type Foo = Arc<_1>; -``` \ No newline at end of file +None \ No newline at end of file From bc1f25af776265aa25a0b65c192c4066feed867c Mon Sep 17 00:00:00 2001 From: varkor Date: Mon, 10 Jun 2019 13:55:59 +0100 Subject: [PATCH 21/22] Remove outdated section --- text/0000-impl-trait-type-aliases.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/text/0000-impl-trait-type-aliases.md b/text/0000-impl-trait-type-aliases.md index c24d54cbc0a..d54ee5b8677 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/0000-impl-trait-type-aliases.md @@ -332,19 +332,6 @@ type Bar<'a> = impl Debug; fn get_bar<'a>(y: &'a ()) -> Bar<'a> { UnitRefWrapper(y) } ``` -## Restricting compound `impl Trait` trait aliases -The type alias syntax is more flexible than `existential type`, but for now we restrict the form to that equivalent to `existential type`. That means that, if `impl Trait` appears on the right-hand side of a type alias declaration, it must be the only type. The following compound type aliases, therefore, are initially forbidden: - -```rust -// error: compound type aliases containing `impl Bar` are disallowed -// hint: extract `impl Bar` from `A` -type A = (impl Bar, impl Bar); -type B = (u8, impl Bar); -type C = Vec; -``` - -This RFC does not prohibit the possibility that this rule could be relaxed in the future if it is found to be too restrictive. - # Drawbacks [drawbacks]: #drawbacks From 758e49d134e524b5118f06ecff8f3a61db4150fb Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sun, 28 Jul 2019 08:12:39 +0200 Subject: [PATCH 22/22] RFC 2515 --- ...it-type-aliases.md => 2515-impl-trait-type-aliases.md} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename text/{0000-impl-trait-type-aliases.md => 2515-impl-trait-type-aliases.md} (98%) diff --git a/text/0000-impl-trait-type-aliases.md b/text/2515-impl-trait-type-aliases.md similarity index 98% rename from text/0000-impl-trait-type-aliases.md rename to text/2515-impl-trait-type-aliases.md index d54ee5b8677..3f06f1d4201 100644 --- a/text/0000-impl-trait-type-aliases.md +++ b/text/2515-impl-trait-type-aliases.md @@ -1,7 +1,7 @@ -- Feature Name: impl_trait_type_aliases +- Feature Name: `impl_trait_type_aliases` - Start Date: 2018-08-03 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: [rust-lang/rfcs#2515](https://github.com/rust-lang/rfcs/pull/2515) +- Rust Issue: [rust-lang/rust#63063](https://github.com/rust-lang/rust/issues/63063) # Summary [summary]: #summary @@ -398,4 +398,4 @@ The other alternatives commonly given are: # Unresolved questions [unresolved-questions]: #unresolved-questions -None \ No newline at end of file +None