From 15ed243554f3e0f201e699e1065ee6e320d53c7a Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Thu, 20 Jul 2017 09:24:13 -0700 Subject: [PATCH 01/13] Add impl Trait type alias and variable declarations --- text/0000-impl-trait-type-alias.md | 537 +++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 text/0000-impl-trait-type-alias.md diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md new file mode 100644 index 00000000000..2ad520554a0 --- /dev/null +++ b/text/0000-impl-trait-type-alias.md @@ -0,0 +1,537 @@ +- Feature Name: impl-trait-type-alias +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Add the ability to create type aliases for `impl Trait` types, +and support `impl Trait` in `let`, `const`, and `static` declarations. + +```rust +// `impl Trait` type alias: +type Adder = impl Fn(usize) -> usize; +fn adder(a: usize) -> Adder { + |b| a + b +} + +// `impl Trait` type aliases in associated type position: +struct MyType; +impl Iterator for MyType { + type Item = impl Debug; + fn next(&mut self) -> Option { + Some("Another item!") + } +} + +// `impl Trait` in `let`, `const`, and `static`: + +const ADD_ONE: impl Fn(usize) -> usize = |x| x + 1; + +static MAYBE_PRINT: Option = Some(|x| println!("{}", x)); + +fn my_func() { + let iter: impl Iterator = (0..5).map(|x| x * 5); + ... +} +``` + +# Motivation +[motivation]: #motivation + +This RFC proposes two expansions to Rust's `impl Trait` feature. +`impl Trait`, first introduced in [RFC 1522][1522], allows functions to return +types which implement a given trait, but whose concrete type remains anonymous. +`impl Trait` was expanded upon in [RFC 1951][1951], which added `impl Trait` to +argument position and resolved questions around syntax and parameter scoping. +In its current form, the feature makes it possible for functions to return +unnameable or complex types such as closures and iterator combinators. +`impl Trait` also allows library authors to hide the concrete type returned by +a function, making it possible to change the return type later on. + +However, the current feature has some severe limitations. +Right now, it isn't possible to return an `impl Trait` type from a trait +implementation. This is a huge restriction which this RFC fixes by making +it possible to create a type alias for an `impl Trait` type using the syntax +`type Foo = impl Trait;`. The type alias syntax also makes it possible to +guarantee that two `impl Trait` types are the same: + +```rust +// `impl Trait` in traits: +struct MyStruct; +impl Iterator for MyStruct { + + // Here we can declare an associated type whose concrete type is hidden + // to other modules. + // + // External users only know that `Item` implements the `Debug` trait. + type Item = impl Debug; + + fn next(&mut self) -> Option { + Some("hello") + } +} +``` + +`impl Trait` type aliases allow us to declare multiple items which refer to +the same `impl Trait` type: + +```rust +// Type `Foo` refers to a type that implements the `Debug` trait. +// The concrete type to which `Foo` refers is inferred from this module, +// and this concrete type is hidden from outer modules (but not submodules). +pub type Foo: impl Debug; + +const FOO: Foo = 5; + +// This function can be used by outer modules to manufacture an instance of +// `Foo`. Other modules don't know the concrete type of `Foo`, +// so they can't make their own `Foo`s. +pub fn get_foo() -> Foo { + 5 +} + +// We know that the argument and return value of `get_larger_foo` must be the +// same type as is returned from `get_foo`. +pub fn get_larger_foo(x: Foo) -> Foo { + let x: i32 = x; + x + 10 +} + +// Since we know that all `Foo`s have the same (hidden) concrete type, we can +// write a function which returns `Foo`s acquired from different places. +fn one_of_the_foos(which: usize) -> Foo { + match which { + 0 => FOO, + 1 => foo1(), + 2 => foo2(), + 3 => opt_foo().unwrap(), + + // It also allows us to make recursive calls to functions with an + // `impl Trait` return type: + x => one_of_the_foos(x - 4), + } +} +``` + +Separately, this RFC adds the ability to store an `impl Trait` type in a +`let`, `const` or `static`. +This makes `const` and `static` declarations more concise, +and makes it possible to store types such as closures or iterator combinators +in `const`s and `static`s. + +In a future world where `const fn` has been expanded to trait functions, +one could imagine iterator constants such as this: + +```rust +const THREES: impl Iterator = (0..).map(|x| x * 3); +``` + +Since the type of `THREES` contains a closure, it is impossible to write down. +The [`const`/`static` type annotation elison RFC][2010] has suggested one +possible solution. +That RFC proposes to let users omit the types of `const`s and `statics`s. +However, in some cases, completely omitting the types of `const` and `static` +items could make it harder to tell what sort of value is being stored in a +`const` or `static`. +Allowing `impl Trait` in `const`s and `static`s would resolve the unnameable +type issue while still allowing users to provide some information about the +type. + +[1522]: https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md +[1951]: https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md +[2010]: https://github.com/rust-lang/rfcs/pull/2010 + +# Guide-Level Explanation +[guide]: #guide + +## Guide: `impl Trait` in `let`, `const` and `static`: +[guide-declarations]: #guide-declarations + +`impl Trait` can be used in `let`, `const`, and `static` declarations, +like this: +```rust +use std::fmt::Display; + +let displayable: impl Display = "Hello, world!"; +println!("{}", displayable); +``` + +Declaring a variable of type `impl Trait` will hide its concrete type +from other modules. The concrete type will only be visible within +the module in which the declaration occurred. +In our example above, this means that, while we can "display" the +value of `displayable`, the concrete type `&str` is hidden to +other modules: + +```rust +use std::fmt::Display; + +// Without `impl Trait`: +mod my_mod { + pub const DISPLAYABLE: &str = "Hello, world!"; + fn in_mod() { + println!("{}", DISPLAYABLE); + assert_eq!(DISPLAYABLE.len(), 5); + } +} + +fn outside_mod() { + println!("{}", my_mod::DISPLAYABLE); + assert_eq!(my_mod::DISPLAYABLE.len(), 5); +} + +// With `impl Trait`: +mod my_mod { + pub const DISPLAYABLE: impl Display = "Hello, world!"; + fn in_mod() { + // Inside the module, the concrete type of DISPLAYABLE is visible + println!("{}", DISPLAYABLE); + assert_eq!(DISPLAYABLE.len(), 5); + } +} + +fn outside_mod() { + // Outside the my_mod, we only know `DISPLAYABLE` implements `Display`. + println!("{}", my_mod::DISPLAYABLE); + + // ERROR: no method `len` on `impl Display` + assert_eq!(my_mod::DISPLAYABLE.len(), 5); +} +``` + +This is useful for declaring a value which implements a trait, +but whose concrete type might change later on. + +`impl Trait` declarations are also useful when declaring constants or +static with types that are impossible to name, like closures: + +```rust +// Without `impl Trait`, we can't declare this constant because we can't +// write down the type of the closure. +const MY_CLOSURE: ??? = |x| x + 1; + +// With `impl Trait`: +const MY_CLOSURE: impl Fn(i32) -> i32 = |x| x + 1; +``` + +## Guide: `impl Trait` Type Aliases +[guide-aliases]: #guide-aliases + +`impl Trait` can also be used to create type aliases: + +```rust +use std::fmt::Debug; + +type Foo = impl Debug; + +fn foo() -> Foo { + 5i32 +} +``` + +`impl Trait` type aliases, just like regular type aliases, create +synonyms for a type. +In the example above, `Foo` is a synonym for `i32`. +The difference between `impl Trait` type aliases and regular type aliases is +that `impl Trait` type aliases hide their concrete type from other modules +(but not submodules). +Only the `impl Trait` signature is exposed: + +```rust +use std::fmt::Debug; + +mod my_mod { + pub type Foo = impl Debug; + + pub fn foo() -> Foo { + 5i32 + } + + pub fn use_foo_inside_mod() -> Foo { + // Creates a variable `x` of type `i32`, which is equal to type `Foo` + let x: i32 = foo(); + x + 5 + } +} + +fn use_foo_outside_mod() { + // Creates a variable `x` of type `Foo`, which is equal to type `impl Debug` + let x = my_mod::foo(); + + // Because we're outside `my_mod`, using a value of type `Foo` as anything + // other than `impl Debug` is an error: + let y: i32 = foo(); // ERROR: expected type `i32`, found type `Foo` +} +``` + +This makes it possible to write modules that hide their concrete types from the +outside world, allowing them to change implementation details without affecting +consumers of their API. + +Note that it is sometimes necessary to manually specify the concrete type of an +`impl Trait`-aliased value, like in `let x: i32 = foo();` above. +This aids the function's ability to locally infer the concrete type of `Foo`. + +One particularly noteworthy use of `impl Trait` type aliases is in trait +implementations. +With this feature, we can declare `impl Trait` associated types: + +```rust +struct MyType; +impl Iterator for MyType { + type Item = impl Debug; + fn next(&mut self) -> Option { + Some("Another item!") + } +} +``` + +In this trait implementation, we've declared that the item returned by our +iterator implements `Debug`, but we've kept its concrete type (`&'static str`) +hidden from the outside world. + +We can even use this feature to specify unnameable associated types, such as +closures: + +```rust +struct MyType; +impl Iterator for MyType { + type Item = impl Fn(i32) -> i32; + fn next(&mut self) -> Option { + Some(|x| x + 5) + } +} +``` + +`impl Trait` aliases can also be used to reference unnameable types in a struct +definition: + +```rust +type Foo = impl Debug; +fn foo() -> Foo { 5i32 } + +struct ContainsFoo { + some_foo: Foo +} +``` + +It's also possible to write generic `impl Trait` aliases: + +```rust +#[derive(Debug)] +struct MyStruct { + inner: T +}; + +type Foo -> impl Debug; + +fn get_foo(x: T) -> Foo { + MyStruct { + inner: x + } +} +``` + +# Reference-Level Explanation +[reference]: #reference + +## Reference: `impl Trait` in `let`, `const` and `static`: +[reference-declarations]: #reference-declarations + +The rules for `impl Trait` values in `let`, `const`, and `static` declarations +work mostly the same as `impl Trait` return values as specified in +[RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md). + +These values hide their concrete type and can only be used as a value which +is known to implement the specified traits. They inherit any type parameters +in scope. One difference from `impl Trait` return types is that they also +inherit any lifetime parameters in scope. This is necessary in order for +`let` bindings to use `impl Trait`. `let` bindings often contain references +which last for anonymous scope-based lifetimes, and annotating these lifetimes +manually would be impossible. + +## Reference: `impl Trait` Type Aliases +[reference-aliases]: #reference-aliases + +`impl Trait` type aliases are similar to normal type aliases, except that their +concrete type is inferred from the module in which they are defined. +For example, the following code has to examine the body of `foo` in order to +determine that the concrete type of `Foo` is `i32`: + +```rust +type Foo = impl Debug; + +fn foo() -> Foo { + 5i32 +} +``` + +This introduces a certain amount of module-level type inference, since `Foo` +can be used in multiple places throughout the module: + +```rust +type Foo = impl Debug; + +fn foo1() -> Foo { + 5i32 +} + +fn foo2() -> Foo { + let x: i32 = foo1(); + x + 5 +} + +fn foo3() -> Foo { + foo1() +} +``` + +Without examining the bodies of each of `foo1`, `foo2`, and `foo3`, it's not +possible for the compiler to know where the concrete type of `Foo` is going +to come from: + +- `foo1` constrains `Foo` because it returns an `i32`, which it claims is equal +to `Foo`. +- `foo2` constraints `Foo`, because it sets the result of function `foo1` +(which returns type `Foo`) to `i32. It then returns that `i32` value as `Foo`, +again constraining `Foo` to the concrete type `i32`. +- `foo3` places no constraints on `Foo` because it merely returns the result +of `foo1`, which is already known to be `Foo`. + +Note that, in order for `foo2` to add `5` to the result of `foo1`, the concrete +type of the result had to be specified. This is because `foo2` cannot infer +the concrete type of `Foo` from other functions in the module. This is done +to prevent the reordering or manipulation of one function from having strange +effects on the typechecking of another function, possibly hidden somewhere far +away in the same module. + +Note that a function can place constraints upon a type other than plain +equality: + +```rust +type Foo = impl Debug; + +fn foo1() -> Foo { + vec![1i32] // Constrains `Foo == Vec` +} + +fn foo2(&mut x: Foo) { + let x: Vec<_> = x; // Constrains `Foo == Vec` for some unknown `T` + + // Removes the first element of `x`. + // This doesn't require knowing the type `T` in `Vec`, so we're able + // to call this function without knowing the full type of `x`. + x.remove(0); +} +``` + +In the above example, `foo1` fully constrains `Foo` to `Vec`, but `foo2` +only partially constrains `Foo` by setting equal to `Vec<_>`. This enables +the use of `Vec<_>` functions, but it isn't enough to infer the full type +of `Foo`. + +Outside of the module, `impl Trait` alias types behave the same way as +other `impl Trait` types, except that it can be assumed that two values with +the same `impl Trait` alias type are actually values of the same type: + +```rust +mod my_mod { + pub type Foo = impl Debug; + pub fn foo() -> Foo { + 5i32 + } + pub fn bar() -> Foo { + 10i32 + } + pub fn baz(x: Foo) -> Foo { + let x: i32 = x; + x + 5 + } +} + +fn outside_mod() -> Foo { + if true { + my_mod::foo() + } else { + my_mod::baz(my_mod::bar()) + } +} +``` + +One last difference between `impl Trait` aliases and normal type aliases is +that `impl Trait` aliases cannot be used in `impl` blocks: + +```rust +type Foo = impl Debug; +impl Foo { // ERROR: `impl` cannot be used on `impl Trait` aliases + ... +} +impl MyTrait for Foo { // ERROR ^ + ... +} +``` + +While this feature may be added at some point in the future, it's unclear +exactly what behavior it should have-- should it result in implementations +of functions and traits on the underlying type? It seems like the answer +should be "no" since doing so would give away the underlying type being +hidden beneath the impl. Still, some version of this feature could be +used eventually to implement traits or functions for closures, or +to express conditional bounds in `impl Trait` signatures +(e.g. `type Foo = impl Debug; impl Clone for Foo { ... }`). +This is a complicated design space which has not yet been explored fully +enough. In the future, such a feature could be added backwards-compatibly. + +# Drawbacks +[drawbacks]: #drawbacks + +This RFC proposes the addition of a complicated feature that will take time +for Rust developers to learn and understand. +There are potentially simpler ways to acheive some of the goals of this RFC, +such as making `impl Trait` usable in traits. +This RFC instead introduces a more complicated solution in order to +allow for increased expressiveness and clarity. + +This RFC makes `impl Trait` feel even more like a type by allowing it in more +locations where formerly only concrete types were allowed. +However, there are other places such a type can appear where `impl Trait` +cannot, such as `impl` blocks and `struct` definitions +(i.e. `struct Foo { x: impl Trait }`). +This inconsistency may be surprising to users. + +Additionally, if Rust ever moves to a bare `Trait` (no `impl`) syntax, +`type Foo = impl Trait;` would likely require a new syntax, as +`type Foo = MyType;` and `type Foo = Trait;` have different-enough +behavior that the syntactic similarity would cause confusion. + +# Alternatives +[alternatives]: #alternatives + +We could instead expand `impl Trait` in a more focused but limited way, +such as specifically extending `impl Trait` to work in traits without +allowing full "`impl Trait` type alias". +A draft RFC for such a proposal can be seen +[here](https://github.com/cramertj/impl-trait-goals/blob/impl-trait-in-traits/0000-impl-trait-in-traits.md). +Any such feature could, in the future, be added as essentially syntax sugar on +top of this RFC, which is strictly more expressive. +The current RFC will also help us to gain experience with how people use +`impl Trait` in practice, allowing us to resolve some remaining questions +in the linked draft, specifically around how `impl Trait` associated types +are used. + +# Unresolved questions +[unresolved]: #unresolved-questions + +The following extensions should be considered in the future: + +- Conditional bounds. Even with this proposal, there's no way to specify +the `impl Trait` bounds necessary to implement traits like `Iterator`, which +have functions whose return types implement traits conditional on the input, +e.g. `fn foo(x: T) -> impl Clone if T: Clone`. +- Associated-type-less `impl Trait` in trait declarations and implementations, +such as the proposal mentioned in the alternatives section. +As mentioned above, this feature would be strictly less expressive than this +RFC. The more general feature proposed in this RFC would help us to define a +better version of this alternative which could be added in the future. From 84b0d6cca90eb3c9e658513defb3499175fff0f2 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Fri, 21 Jul 2017 09:26:43 -0700 Subject: [PATCH 02/13] Fix type alias typos --- text/0000-impl-trait-type-alias.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 2ad520554a0..c72a9ff8299 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -81,7 +81,7 @@ the same `impl Trait` type: // Type `Foo` refers to a type that implements the `Debug` trait. // The concrete type to which `Foo` refers is inferred from this module, // and this concrete type is hidden from outer modules (but not submodules). -pub type Foo: impl Debug; +pub type Foo = impl Debug; const FOO: Foo = 5; @@ -325,7 +325,7 @@ struct MyStruct { inner: T }; -type Foo -> impl Debug; +type Foo = impl Debug; fn get_foo(x: T) -> Foo { MyStruct { From fc66e989db627f64d0bbc6a86c621f53f7709147 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Fri, 21 Jul 2017 12:24:13 -0700 Subject: [PATCH 03/13] Add missing quote --- text/0000-impl-trait-type-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index c72a9ff8299..6155e1ee7bd 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -395,7 +395,7 @@ to come from: - `foo1` constrains `Foo` because it returns an `i32`, which it claims is equal to `Foo`. - `foo2` constraints `Foo`, because it sets the result of function `foo1` -(which returns type `Foo`) to `i32. It then returns that `i32` value as `Foo`, +(which returns type `Foo`) to `i32`. It then returns that `i32` value as `Foo`, again constraining `Foo` to the concrete type `i32`. - `foo3` places no constraints on `Foo` because it merely returns the result of `foo1`, which is already known to be `Foo`. From 46dbc6817218ecfe17b27360ef0f2cc03c3df713 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Fri, 4 Aug 2017 21:45:03 -0700 Subject: [PATCH 04/13] Completely hide the concrete type of impl Trait bindings --- text/0000-impl-trait-type-alias.md | 83 +++++++++++++++++++----------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 6155e1ee7bd..61b84c8ca5b 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -158,52 +158,36 @@ let displayable: impl Display = "Hello, world!"; println!("{}", displayable); ``` -Declaring a variable of type `impl Trait` will hide its concrete type -from other modules. The concrete type will only be visible within -the module in which the declaration occurred. +Declaring a variable of type `impl Trait` will hide its concrete type. +This is useful for declaring a value which implements a trait, +but whose concrete type might change later on. In our example above, this means that, while we can "display" the -value of `displayable`, the concrete type `&str` is hidden to -other modules: +value of `displayable`, the concrete type `&str` is hidden: ```rust use std::fmt::Display; // Without `impl Trait`: -mod my_mod { - pub const DISPLAYABLE: &str = "Hello, world!"; - fn in_mod() { - println!("{}", DISPLAYABLE); - assert_eq!(DISPLAYABLE.len(), 5); - } -} - -fn outside_mod() { - println!("{}", my_mod::DISPLAYABLE); - assert_eq!(my_mod::DISPLAYABLE.len(), 5); +const DISPLAYABLE: &str = "Hello, world!"; +fn display() { + println!("{}", DISPLAYABLE); + assert_eq!(DISPLAYABLE.len(), 5); } // With `impl Trait`: -mod my_mod { - pub const DISPLAYABLE: impl Display = "Hello, world!"; - fn in_mod() { - // Inside the module, the concrete type of DISPLAYABLE is visible - println!("{}", DISPLAYABLE); - assert_eq!(DISPLAYABLE.len(), 5); - } -} +const DISPLAYABLE: impl Display = "Hello, world!"; -fn outside_mod() { - // Outside the my_mod, we only know `DISPLAYABLE` implements `Display`. - println!("{}", my_mod::DISPLAYABLE); +fn display() { + // We know `DISPLAYABLE` implements `Display`. + println!("{}", DISPLAYABLE); // ERROR: no method `len` on `impl Display` - assert_eq!(my_mod::DISPLAYABLE.len(), 5); + // We don't know the concrete type of `DISPLAYABLE`, + // so we don't know that it has a `len` method. + assert_eq!(DISPLAYABLE.len(), 5); } ``` -This is useful for declaring a value which implements a trait, -but whose concrete type might change later on. - `impl Trait` declarations are also useful when declaring constants or static with types that are impossible to name, like closures: @@ -216,6 +200,43 @@ const MY_CLOSURE: ??? = |x| x + 1; const MY_CLOSURE: impl Fn(i32) -> i32 = |x| x + 1; ``` +Finally, note that `impl Trait` `let` declarations hide the concrete +types of local variables: + +```rust +let displayable: impl Display = "Hello, world!"; + +// We know `displayable` implements `Display`. +println!("{}", displayable); + +// ERROR: no method `len` on `impl Display` +// We don't know the concrete type of `displayable`, +// so we don't know that it has a `len` method. +assert_eq!(displayable.len(), 5); +``` + +At first glance, this behavior doesn't seem particularly useful. +Indeed, `impl Trait` in `let` bindings exists mostly for consistency with +`const`s and `static`s. However, it can be useful for documenting the +specific ways in which a variable is used. It can also be used to provide +better error messages for complex, nested types: + +```rust +// Without `impl Trait`: +let x = (0..100).map(|x| x * 3).filter(|x| x % 5 == 0); + +// ERROR: no method named `bogus_missing_method` found for type +// `std::iter::Filter, [closure@src/main.rs:2:26: 2:35]>, [closure@src/main.rs:2:44: 2:58]>` in the current scope +x.bogus_missing_method(); + +// With `impl Trait`: +let x: impl Iterator = (0..100).map(|x| x * 3).filter(|x| x % 5); + +// ERROR: no method named `bogus_missing_method` found for type +// `impl std::iter::Iterator` in the current scope +x.bogus_missing_method(); +``` + ## Guide: `impl Trait` Type Aliases [guide-aliases]: #guide-aliases From bb09a0b33f8178b97dc1d1c2231fc86b0801e528 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Sat, 5 Aug 2017 00:35:18 -0700 Subject: [PATCH 05/13] Add composite impl trait type alias example and syntax alternatives --- text/0000-impl-trait-type-alias.md | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 61b84c8ca5b..4712ff1af54 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -355,6 +355,24 @@ fn get_foo(x: T) -> Foo { } ``` +`impl Trait` can also appear inside of another type in a type alias: + +```rust +type Foo = Option; +fn foo() -> Foo { + Some("Debuggable") +} +``` + +Or even multiple times within the same type alias: + +```rust +type Foo = (impl Debug, impl Fn()); +fn foo() -> Foo { + ("Debuggable", || println!("Hello, world!")) +} +``` + # Reference-Level Explanation [reference]: #reference @@ -542,6 +560,24 @@ The current RFC will also help us to gain experience with how people use in the linked draft, specifically around how `impl Trait` associated types are used. +There are a number of alternative syntaxes we could use for `impl Trait` +aliases: +- `abstype / abstract type Foo: Trait;`: Suggested in +[RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), +this syntax has the potential advantage of being able to specify constraints +such as `abstract type Foo: Trait = MyType;`, which provides module-level +abstraction without relying upon module-level inference. +- `type Foo: Trait;`: This option also has the "abstraction without inference" +advantage. However, it doesn't include an easily searchable keyword like +`abstract/abstype/impl`, so it might be hard for users to discover what's going +on when they first encounter this syntax. +- `type Foo = impl Trait;`: This is the syntax option I've used in this RFC. +It is the only option which doesn't allow for "abstraction without inference", +but it is also the only option which allows for "composite" `impl Trait` types +such as `type Foo = (impl Debug, impl Fn());`. It also bears a syntactic +resemblance to the `impl Trait` feature, which should make it easy for new users +to identify and understand. + # Unresolved questions [unresolved]: #unresolved-questions From f9f10ea210b1c6ef8142bc5f8e6959e2bc8f1e36 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Sat, 5 Aug 2017 09:48:36 -0700 Subject: [PATCH 06/13] Fix missing path --- text/0000-impl-trait-type-alias.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 4712ff1af54..84e5438d910 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -283,7 +283,7 @@ fn use_foo_outside_mod() { // Because we're outside `my_mod`, using a value of type `Foo` as anything // other than `impl Debug` is an error: - let y: i32 = foo(); // ERROR: expected type `i32`, found type `Foo` + let y: i32 = my_mod::foo(); // ERROR: expected type `i32`, found type `Foo` } ``` From 6deaab99718f057bf149288dce332dc53bde0b2f Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Sun, 6 Aug 2017 23:46:30 -0700 Subject: [PATCH 07/13] Remove partial bounds for impl trait aliases --- text/0000-impl-trait-type-alias.md | 92 ++++++++++++++---------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 84e5438d910..7b5fcb26180 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -395,7 +395,7 @@ manually would be impossible. [reference-aliases]: #reference-aliases `impl Trait` type aliases are similar to normal type aliases, except that their -concrete type is inferred from the module in which they are defined. +concrete type is determined from the module in which they are defined. For example, the following code has to examine the body of `foo` in order to determine that the concrete type of `Foo` is `i32`: @@ -407,70 +407,57 @@ fn foo() -> Foo { } ``` -This introduces a certain amount of module-level type inference, since `Foo` -can be used in multiple places throughout the module: +`Foo` can be used as `i32` in multiple places throughout the module. +However, each function that uses `Foo` as `i32` must independently place +constraints upon `Foo` such that it *must* be `i32`: ```rust -type Foo = impl Debug; - -fn foo1() -> Foo { - 5i32 +fn add_to_foo_1(x: Foo) { + x + 1 // ERROR: binary operation `+` cannot be applied to type `impl Debug` +// ^ `x` here is type `impl Debug`. +// Type annotations needed to resolve the concrete type of `x`. +// (^ This particular error should only appear within the module in which +// `Foo` is defined) } -fn foo2() -> Foo { - let x: i32 = foo1(); - x + 5 -} - -fn foo3() -> Foo { - foo1() +fn add_to_foo_2(x: Foo) { + let x: i32 = x; + x + 1 } ``` -Without examining the bodies of each of `foo1`, `foo2`, and `foo3`, it's not -possible for the compiler to know where the concrete type of `Foo` is going -to come from: +Each instance of `impl Trait` in a type alias must have at least one function +which constrains it to a concrete type. A function must either fully +constrain or place no constraints upon a given instance of `impl Trait` in +a type alias. -- `foo1` constrains `Foo` because it returns an `i32`, which it claims is equal -to `Foo`. -- `foo2` constraints `Foo`, because it sets the result of function `foo1` -(which returns type `Foo`) to `i32`. It then returns that `i32` value as `Foo`, -again constraining `Foo` to the concrete type `i32`. -- `foo3` places no constraints on `Foo` because it merely returns the result -of `foo1`, which is already known to be `Foo`. - -Note that, in order for `foo2` to add `5` to the result of `foo1`, the concrete -type of the result had to be specified. This is because `foo2` cannot infer -the concrete type of `Foo` from other functions in the module. This is done -to prevent the reordering or manipulation of one function from having strange -effects on the typechecking of another function, possibly hidden somewhere far -away in the same module. - -Note that a function can place constraints upon a type other than plain -equality: +The following is an example of an `impl Trait` type alias which contains +two instances of `impl Trait`. Each instance is determined by exactly one +of the functions: ```rust -type Foo = impl Debug; +// The concrete type of `Baz` resolves to `(i32, &'static str)` +type Baz = (impl Default + Debug, impl Default + Debug); -fn foo1() -> Foo { - vec![1i32] // Constrains `Foo == Vec` +// This function places no constraints on the `impl Trait` types +fn new_baz() -> Baz { + (Default::default(), Default::default()) } -fn foo2(&mut x: Foo) { - let x: Vec<_> = x; // Constrains `Foo == Vec` for some unknown `T` +// This function fully constraints the first `impl Trait` type, +// but places no constraints upon the second. +fn add_to_first(baz: Baz) -> Baz { + let first: i32 = baz.0; + (first + 1, baz.1) +} - // Removes the first element of `x`. - // This doesn't require knowing the type `T` in `Vec`, so we're able - // to call this function without knowing the full type of `x`. - x.remove(0); +// This function fully constrains the second `impl Trait` type, +// but places no constraints upon the first. +fn make_second_hello(baz: Baz) -> Baz { + (baz.0, "Hello, world!") } ``` -In the above example, `foo1` fully constrains `Foo` to `Vec`, but `foo2` -only partially constrains `Foo` by setting equal to `Vec<_>`. This enables -the use of `Vec<_>` functions, but it isn't enough to infer the full type -of `Foo`. - Outside of the module, `impl Trait` alias types behave the same way as other `impl Trait` types, except that it can be assumed that two values with the same `impl Trait` alias type are actually values of the same type: @@ -592,3 +579,12 @@ such as the proposal mentioned in the alternatives section. As mentioned above, this feature would be strictly less expressive than this RFC. The more general feature proposed in this RFC would help us to define a better version of this alternative which could be added in the future. +- A more general form of inference for `impl Trait` type aliases. This RFC +forces each function to either fully constrain or place no constraints upon +an `impl Trait` type. It's possible to allow some partial constraints through +a process like the one described in +[this comment](https://github.com/rust-lang/rfcs/pull/2071#issuecomment-320458113). +However, these partial bounds present implementation concerns, so they have +been removed from this RFC. If it turns out that partial bounds would be +greatly useful in practice, they can be added backwards-compatibly in a future +RFC. From f0b8a8bd9dfd833e456c13c08c564769512641cc Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Mon, 7 Aug 2017 10:54:57 -0700 Subject: [PATCH 08/13] Clarify that impl trait aliases can be constrained by consts/statics --- text/0000-impl-trait-type-alias.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 7b5fcb26180..7b928ade479 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -426,10 +426,10 @@ fn add_to_foo_2(x: Foo) { } ``` -Each instance of `impl Trait` in a type alias must have at least one function -which constrains it to a concrete type. A function must either fully -constrain or place no constraints upon a given instance of `impl Trait` in -a type alias. +Each instance of `impl Trait` in a type alias must be constrained by at least +one function body or const/static initializer. +A body or initializer must either fully constrain or place no constraints upon +a given instance of `impl Trait` in a type alias. The following is an example of an `impl Trait` type alias which contains two instances of `impl Trait`. Each instance is determined by exactly one From b4a57eff41fb3747c63b0720b5a55d7c1a2caeca Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Fri, 25 Aug 2017 00:44:05 -0700 Subject: [PATCH 09/13] Clarify generics and scoping behavior --- text/0000-impl-trait-type-alias.md | 58 +++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 7b928ade479..22fd38e0c4d 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -338,6 +338,25 @@ struct ContainsFoo { } ``` + +`impl Trait` can also appear inside of another type in a type alias: + +```rust +type Foo = Option; +fn foo() -> Foo { + Some("Debuggable") +} +``` + +Or even multiple times within the same type alias: + +```rust +type Foo = (impl Debug, impl Fn()); +fn foo() -> Foo { + ("Debuggable", || println!("Hello, world!")) +} +``` + It's also possible to write generic `impl Trait` aliases: ```rust @@ -355,24 +374,31 @@ fn get_foo(x: T) -> Foo { } ``` -`impl Trait` can also appear inside of another type in a type alias: +As specified in +[RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), +`impl Trait` implicitly captures all generic types parameters in scope. +In practice, this means that `impl Trait` associated types may contain generic +types from an impl: ```rust -type Foo = Option; -fn foo() -> Foo { - Some("Debuggable") +struct MyStruct; +trait Foo { + type Bar; + fn bar() -> Bar; } -``` - -Or even multiple times within the same type alias: -```rust -type Foo = (impl Debug, impl Fn()); -fn foo() -> Foo { - ("Debuggable", || println!("Hello, world!")) +impl Foo for MyStruct { + type Bar = impl Trait; + fn bar() -> impl Trait { + ... + // Returns some type MyBar + } } ``` +However, as in 1951, `impl Trait` lifetime parameters must be explicitly +annotated. + # Reference-Level Explanation [reference]: #reference @@ -395,7 +421,8 @@ manually would be impossible. [reference-aliases]: #reference-aliases `impl Trait` type aliases are similar to normal type aliases, except that their -concrete type is determined from the module in which they are defined. +concrete type is determined from the scope in which they are defined +(usually a module or a trait impl). For example, the following code has to examine the body of `foo` in order to determine that the concrete type of `Foo` is `i32`: @@ -424,6 +451,13 @@ fn add_to_foo_2(x: Foo) { let x: i32 = x; x + 1 } + +fn return_foo(x: Foo) -> Foo { + // This is allowed. + // We don't need to know the concrete type of `Foo` for this function to + // typecheck. + x +} ``` Each instance of `impl Trait` in a type alias must be constrained by at least From 0a9907502be390d9ddc7b3206afb36762ace50d6 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Wed, 30 Aug 2017 10:18:45 -0700 Subject: [PATCH 10/13] Fix typos --- text/0000-impl-trait-type-alias.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 22fd38e0c4d..89bdd894699 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -389,7 +389,7 @@ trait Foo { impl Foo for MyStruct { type Bar = impl Trait; - fn bar() -> impl Trait { + fn bar() -> Self::Bar { ... // Returns some type MyBar } @@ -478,7 +478,7 @@ fn new_baz() -> Baz { (Default::default(), Default::default()) } -// This function fully constraints the first `impl Trait` type, +// This function fully constrains the first `impl Trait` type, // but places no constraints upon the second. fn add_to_first(baz: Baz) -> Baz { let first: i32 = baz.0; @@ -549,7 +549,7 @@ enough. In the future, such a feature could be added backwards-compatibly. This RFC proposes the addition of a complicated feature that will take time for Rust developers to learn and understand. -There are potentially simpler ways to acheive some of the goals of this RFC, +There are potentially simpler ways to achieve some of the goals of this RFC, such as making `impl Trait` usable in traits. This RFC instead introduces a more complicated solution in order to allow for increased expressiveness and clarity. From bae6b1c9b6336fa38ac95f9de21deecf47b04fab Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sun, 17 Sep 2017 15:28:48 -0700 Subject: [PATCH 11/13] Rewrite to include abstract type syntax. --- text/0000-impl-trait-type-alias.md | 171 +++++++++++++++-------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index 89bdd894699..a7f318ab5fb 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -1,25 +1,26 @@ -- Feature Name: impl-trait-type-alias -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Feature Name: impl-trait-abstract-types +- Start Date: 2017-07-20 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) # Summary [summary]: #summary -Add the ability to create type aliases for `impl Trait` types, -and support `impl Trait` in `let`, `const`, and `static` declarations. +Add the ability to create named "abstract types," which are existential types +and a desugaring for `impl Trait` return types. Also support `impl Trait` in +`let`, `const`, and `static` declarations. ```rust -// `impl Trait` type alias: -type Adder = impl Fn(usize) -> usize; +// abstract type +abstract type Adder: Fn(usize) -> usize; fn adder(a: usize) -> Adder { |b| a + b } -// `impl Trait` type aliases in associated type position: +// abstract type in associated type position: struct MyType; impl Iterator for MyType { - type Item = impl Debug; + abstract type Item: Debug; fn next(&mut self) -> Option { Some("Another item!") } @@ -28,9 +29,7 @@ impl Iterator for MyType { // `impl Trait` in `let`, `const`, and `static`: const ADD_ONE: impl Fn(usize) -> usize = |x| x + 1; - static MAYBE_PRINT: Option = Some(|x| println!("{}", x)); - fn my_func() { let iter: impl Iterator = (0..5).map(|x| x * 5); ... @@ -53,9 +52,9 @@ a function, making it possible to change the return type later on. However, the current feature has some severe limitations. Right now, it isn't possible to return an `impl Trait` type from a trait implementation. This is a huge restriction which this RFC fixes by making -it possible to create a type alias for an `impl Trait` type using the syntax -`type Foo = impl Trait;`. The type alias syntax also makes it possible to -guarantee that two `impl Trait` types are the same: +it possible to create a named abstract type using the `abstract type` syntax. +This syntax also makes it possible that two anonymous return types are the same +type: ```rust // `impl Trait` in traits: @@ -66,7 +65,7 @@ impl Iterator for MyStruct { // to other modules. // // External users only know that `Item` implements the `Debug` trait. - type Item = impl Debug; + abstract type Item: Debug; fn next(&mut self) -> Option { Some("hello") @@ -74,14 +73,14 @@ impl Iterator for MyStruct { } ``` -`impl Trait` type aliases allow us to declare multiple items which refer to -the same `impl Trait` type: +Abstract types allow us to declare multiple items which refer to +the same abstract type: ```rust // Type `Foo` refers to a type that implements the `Debug` trait. // The concrete type to which `Foo` refers is inferred from this module, // and this concrete type is hidden from outer modules (but not submodules). -pub type Foo = impl Debug; +pub abstract type Foo: Debug; const FOO: Foo = 5; @@ -237,34 +236,34 @@ let x: impl Iterator = (0..100).map(|x| x * 3).filter(|x| x % 5); x.bogus_missing_method(); ``` -## Guide: `impl Trait` Type Aliases -[guide-aliases]: #guide-aliases +## Guide: Abstract types +[guide-abstract]: #guide-abstract -`impl Trait` can also be used to create type aliases: +In addition to `impl Trait`, we provide a new `abstract type` syntax, which +functions as a desugaring for `impl Trait` in return types and the new +positions here, similar to how generics are the desugaring in parameter +position: ```rust use std::fmt::Debug; -type Foo = impl Debug; +abstract type Foo: Debug; fn foo() -> Foo { 5i32 } ``` -`impl Trait` type aliases, just like regular type aliases, create -synonyms for a type. -In the example above, `Foo` is a synonym for `i32`. -The difference between `impl Trait` type aliases and regular type aliases is -that `impl Trait` type aliases hide their concrete type from other modules -(but not submodules). -Only the `impl Trait` signature is exposed: +An abstract type allows you to give a name to a type without revealing exactly +what type is being used. In the example above, `Foo` refers to `i32`, but the +abstract type hides that (unlike a normal type alias). Only the traits which +the abstract type is declared to implement are exposed: ```rust use std::fmt::Debug; mod my_mod { - pub type Foo = impl Debug; + pub abstract type Foo: Debug; pub fn foo() -> Foo { 5i32 @@ -278,12 +277,14 @@ mod my_mod { } fn use_foo_outside_mod() { - // Creates a variable `x` of type `Foo`, which is equal to type `impl Debug` + // Creates a variable `x` of type `Foo`, which is only known to implement `Debug` let x = my_mod::foo(); - // Because we're outside `my_mod`, using a value of type `Foo` as anything - // other than `impl Debug` is an error: - let y: i32 = my_mod::foo(); // ERROR: expected type `i32`, found type `Foo` + // Because we're outside `my_mod`, the user cannot determine the type of `Foo`. + let y: i32 = my_mod::foo(); // ERROR: expected type `i32`, found abstract type `Foo` + + // However, the user can use its `Debug` impl: + println!("{:?}", x); } ``` @@ -292,17 +293,17 @@ outside world, allowing them to change implementation details without affecting consumers of their API. Note that it is sometimes necessary to manually specify the concrete type of an -`impl Trait`-aliased value, like in `let x: i32 = foo();` above. -This aids the function's ability to locally infer the concrete type of `Foo`. +abstract type, like in `let x: i32 = foo();` above. This aids the function's +ability to locally infer the concrete type of `Foo`. One particularly noteworthy use of `impl Trait` type aliases is in trait implementations. -With this feature, we can declare `impl Trait` associated types: +With this feature, we can declare associated types as abstract: ```rust struct MyType; impl Iterator for MyType { - type Item = impl Debug; + abstract type Item: Debug; fn next(&mut self) -> Option { Some("Another item!") } @@ -319,18 +320,18 @@ closures: ```rust struct MyType; impl Iterator for MyType { - type Item = impl Fn(i32) -> i32; + abstract type Item: Fn(i32) -> i32; fn next(&mut self) -> Option { Some(|x| x + 5) } } ``` -`impl Trait` aliases can also be used to reference unnameable types in a struct +Abstract types can also be used to reference unnameable types in a struct definition: ```rust -type Foo = impl Debug; +abstract type Foo: Debug; fn foo() -> Foo { 5i32 } struct ContainsFoo { @@ -339,25 +340,7 @@ struct ContainsFoo { ``` -`impl Trait` can also appear inside of another type in a type alias: - -```rust -type Foo = Option; -fn foo() -> Foo { - Some("Debuggable") -} -``` - -Or even multiple times within the same type alias: - -```rust -type Foo = (impl Debug, impl Fn()); -fn foo() -> Foo { - ("Debuggable", || println!("Hello, world!")) -} -``` - -It's also possible to write generic `impl Trait` aliases: +It's also possible to write generic abstract types: ```rust #[derive(Debug)] @@ -365,7 +348,7 @@ struct MyStruct { inner: T }; -type Foo = impl Debug; +abstract type Foo: Debug; fn get_foo(x: T) -> Foo { MyStruct { @@ -374,11 +357,11 @@ fn get_foo(x: T) -> Foo { } ``` -As specified in +Similarly to `impl Trait` under [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), -`impl Trait` implicitly captures all generic types parameters in scope. -In practice, this means that `impl Trait` associated types may contain generic -types from an impl: +`abstract type` implicitly captures all generic type parameters in scope. In +practice, this means that abstract associated types may contain generic +parameters from their impl: ```rust struct MyStruct; @@ -388,7 +371,7 @@ trait Foo { } impl Foo for MyStruct { - type Bar = impl Trait; + abstract type Bar: Trait; fn bar() -> Self::Bar { ... // Returns some type MyBar @@ -396,8 +379,7 @@ impl Foo for MyStruct { } ``` -However, as in 1951, `impl Trait` lifetime parameters must be explicitly -annotated. +However, as in 1951, lifetime parameters must be explicitly annotated. # Reference-Level Explanation [reference]: #reference @@ -581,28 +563,47 @@ The current RFC will also help us to gain experience with how people use in the linked draft, specifically around how `impl Trait` associated types are used. -There are a number of alternative syntaxes we could use for `impl Trait` -aliases: -- `abstype / abstract type Foo: Trait;`: Suggested in -[RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), -this syntax has the potential advantage of being able to specify constraints -such as `abstract type Foo: Trait = MyType;`, which provides module-level -abstraction without relying upon module-level inference. -- `type Foo: Trait;`: This option also has the "abstraction without inference" -advantage. However, it doesn't include an easily searchable keyword like -`abstract/abstype/impl`, so it might be hard for users to discover what's going -on when they first encounter this syntax. -- `type Foo = impl Trait;`: This is the syntax option I've used in this RFC. -It is the only option which doesn't allow for "abstraction without inference", -but it is also the only option which allows for "composite" `impl Trait` types -such as `type Foo = (impl Debug, impl Fn());`. It also bears a syntactic -resemblance to the `impl Trait` feature, which should make it easy for new users -to identify and understand. +Throughout the process we have considered a number of alternative syntaxes for +abstract types. There are a variety of small changes, similar to the proposal +here: + +- Instead of `abstract type`, it could be some single keyword like `abstype`. +- We could use a different keyword from `abstract`, like `opaque`. +- We could not include the `abstract` keyword at all, and just have a type +alias with only a bound and not not an `=` be "abstract." + +A more divergent alternative is not to have an "abstract type" feature at all, +but instead just have `impl Trait` be allowed in type alias position. +Everything written `abstract type $NAME: $BOUND;` in this RFC would instead be +written `type $NAME = impl $BOUND;`. + +The RFC settled on the abstract type syntax because we believe it will act as a +teaching aid. As a result of [RFC 1951][1951], `impl Trait` is sometimes +universal quantiifcation and sometimes existential quantification. By providing +a separate syntax for "explicit" existential quantification, `impl Trait` can +be taught as a syntactic sugar for generics and abstract types. By "just using +`impl Trait`," there would be no "bottom" explanation for what it means when it +is used as existential quantification. + +This choice has some disadvantages in comparison impl Trait in type aliases: + +- We introduce another new syntax on top of `impl Trait`, which inherently has +some costs. +- Users can't use it in a nested fashion without creating an addiitonal +abstract type. + +Because of these downsides, we are open to reconsidering this question with +more practical experience, and the final syntax is left as an unresolved +question for the RFC. # Unresolved questions [unresolved]: #unresolved-questions -The following extensions should be considered in the future: +As discussed in the [alternatives][alternatives] section above, we intend to +reconsider whether `abstract type` is the optimal syntax before stabilizing +this feature. + +Additionally, the following extensions should be considered in the future: - Conditional bounds. Even with this proposal, there's no way to specify the `impl Trait` bounds necessary to implement traits like `Iterator`, which From 14423ffbf7a2e817303249da5e6324464bbc0906 Mon Sep 17 00:00:00 2001 From: Taylor Cramer Date: Sun, 17 Sep 2017 16:57:47 -0700 Subject: [PATCH 12/13] The existential crisis continues Temporarily replace the abstract type syntax with existential type syntax. --- text/0000-impl-trait-type-alias.md | 186 ++++++++++++----------------- 1 file changed, 78 insertions(+), 108 deletions(-) diff --git a/text/0000-impl-trait-type-alias.md b/text/0000-impl-trait-type-alias.md index a7f318ab5fb..64fe192ce28 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/0000-impl-trait-type-alias.md @@ -1,4 +1,4 @@ -- Feature Name: impl-trait-abstract-types +- Feature Name: impl-trait-existential-types - Start Date: 2017-07-20 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) @@ -6,21 +6,20 @@ # Summary [summary]: #summary -Add the ability to create named "abstract types," which are existential types -and a desugaring for `impl Trait` return types. Also support `impl Trait` in -`let`, `const`, and `static` declarations. +Add the ability to create named existential types and +support `impl Trait` in `let`, `const`, and `static` declarations. ```rust -// abstract type -abstract type Adder: Fn(usize) -> usize; +// existential types +existential type Adder: Fn(usize) -> usize; fn adder(a: usize) -> Adder { |b| a + b } -// abstract type in associated type position: +// existential type in associated type position: struct MyType; impl Iterator for MyType { - abstract type Item: Debug; + existential type Item: Debug; fn next(&mut self) -> Option { Some("Another item!") } @@ -52,9 +51,7 @@ a function, making it possible to change the return type later on. However, the current feature has some severe limitations. Right now, it isn't possible to return an `impl Trait` type from a trait implementation. This is a huge restriction which this RFC fixes by making -it possible to create a named abstract type using the `abstract type` syntax. -This syntax also makes it possible that two anonymous return types are the same -type: +it possible to create a named existential type: ```rust // `impl Trait` in traits: @@ -65,7 +62,7 @@ impl Iterator for MyStruct { // to other modules. // // External users only know that `Item` implements the `Debug` trait. - abstract type Item: Debug; + existential type Item: Debug; fn next(&mut self) -> Option { Some("hello") @@ -73,14 +70,14 @@ impl Iterator for MyStruct { } ``` -Abstract types allow us to declare multiple items which refer to -the same abstract type: +This syntax allows us to declare multiple items which refer to +the same existential type: ```rust // Type `Foo` refers to a type that implements the `Debug` trait. // The concrete type to which `Foo` refers is inferred from this module, // and this concrete type is hidden from outer modules (but not submodules). -pub abstract type Foo: Debug; +pub existential type Foo: Debug; const FOO: Foo = 5; @@ -236,34 +233,36 @@ let x: impl Iterator = (0..100).map(|x| x * 3).filter(|x| x % 5); x.bogus_missing_method(); ``` -## Guide: Abstract types -[guide-abstract]: #guide-abstract +## Guide: Existential types +[guide-existential]: #guide-existential -In addition to `impl Trait`, we provide a new `abstract type` syntax, which -functions as a desugaring for `impl Trait` in return types and the new -positions here, similar to how generics are the desugaring in parameter -position: +Rust allows users to declare `existential type`s. +An existential type allows you to give a name to a type without revealing +exactly what type is being used. ```rust use std::fmt::Debug; -abstract type Foo: Debug; +existential type Foo: Debug; fn foo() -> Foo { 5i32 } ``` -An abstract type allows you to give a name to a type without revealing exactly -what type is being used. In the example above, `Foo` refers to `i32`, but the -abstract type hides that (unlike a normal type alias). Only the traits which -the abstract type is declared to implement are exposed: +In the example above, `Foo` refers to `i32`, similar to a type alias. +However, unlike a normal type alias, the concrete type of `Foo` is +hidden outside of the module. Outside the module, the only think that +is known about `Foo` is that it implements the traits that appear in +its declaration (e.g. `Debug` in `existential type Foo: Debug;`). +If a user outside the module tries to use a `Foo` as an `i32`, they +will see an error: ```rust use std::fmt::Debug; mod my_mod { - pub abstract type Foo: Debug; + pub existential type Foo: Debug; pub fn foo() -> Foo { 5i32 @@ -281,7 +280,7 @@ fn use_foo_outside_mod() { let x = my_mod::foo(); // Because we're outside `my_mod`, the user cannot determine the type of `Foo`. - let y: i32 = my_mod::foo(); // ERROR: expected type `i32`, found abstract type `Foo` + let y: i32 = my_mod::foo(); // ERROR: expected type `i32`, found existential type `Foo` // However, the user can use its `Debug` impl: println!("{:?}", x); @@ -293,17 +292,17 @@ outside world, allowing them to change implementation details without affecting consumers of their API. Note that it is sometimes necessary to manually specify the concrete type of an -abstract type, like in `let x: i32 = foo();` above. This aids the function's +existential type, like in `let x: i32 = foo();` above. This aids the function's ability to locally infer the concrete type of `Foo`. -One particularly noteworthy use of `impl Trait` type aliases is in trait +One particularly noteworthy use of existential types is in trait implementations. -With this feature, we can declare associated types as abstract: +With this feature, we can declare associated types as follows: ```rust struct MyType; impl Iterator for MyType { - abstract type Item: Debug; + existential type Item: Debug; fn next(&mut self) -> Option { Some("Another item!") } @@ -320,18 +319,18 @@ closures: ```rust struct MyType; impl Iterator for MyType { - abstract type Item: Fn(i32) -> i32; + existential type Item: Fn(i32) -> i32; fn next(&mut self) -> Option { Some(|x| x + 5) } } ``` -Abstract types can also be used to reference unnameable types in a struct +Existential types can also be used to reference unnameable types in a struct definition: ```rust -abstract type Foo: Debug; +existential type Foo: Debug; fn foo() -> Foo { 5i32 } struct ContainsFoo { @@ -340,7 +339,7 @@ struct ContainsFoo { ``` -It's also possible to write generic abstract types: +It's also possible to write generic existential types: ```rust #[derive(Debug)] @@ -348,7 +347,7 @@ struct MyStruct { inner: T }; -abstract type Foo: Debug; +existential type Foo: Debug; fn get_foo(x: T) -> Foo { MyStruct { @@ -359,8 +358,8 @@ fn get_foo(x: T) -> Foo { Similarly to `impl Trait` under [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), -`abstract type` implicitly captures all generic type parameters in scope. In -practice, this means that abstract associated types may contain generic +`existential type` implicitly captures all generic type parameters in scope. In +practice, this means that existential associated types may contain generic parameters from their impl: ```rust @@ -371,7 +370,7 @@ trait Foo { } impl Foo for MyStruct { - abstract type Bar: Trait; + existentail type Bar: Trait; fn bar() -> Self::Bar { ... // Returns some type MyBar @@ -399,17 +398,17 @@ inherit any lifetime parameters in scope. This is necessary in order for which last for anonymous scope-based lifetimes, and annotating these lifetimes manually would be impossible. -## Reference: `impl Trait` Type Aliases -[reference-aliases]: #reference-aliases +## Reference: Existential Types +[reference-existential]: #reference-existential -`impl Trait` type aliases are similar to normal type aliases, except that their +Existential types are similar to normal type aliases, except that their concrete type is determined from the scope in which they are defined (usually a module or a trait impl). For example, the following code has to examine the body of `foo` in order to determine that the concrete type of `Foo` is `i32`: ```rust -type Foo = impl Debug; +existential type Foo = impl Debug; fn foo() -> Foo { 5i32 @@ -422,8 +421,8 @@ constraints upon `Foo` such that it *must* be `i32`: ```rust fn add_to_foo_1(x: Foo) { - x + 1 // ERROR: binary operation `+` cannot be applied to type `impl Debug` -// ^ `x` here is type `impl Debug`. + x + 1 // ERROR: binary operation `+` cannot be applied to existential type `Foo` +// ^ `x` here is type `Foo`. // Type annotations needed to resolve the concrete type of `x`. // (^ This particular error should only appear within the module in which // `Foo` is defined) @@ -442,45 +441,19 @@ fn return_foo(x: Foo) -> Foo { } ``` -Each instance of `impl Trait` in a type alias must be constrained by at least +Each existential type declaration must be constrained by at least one function body or const/static initializer. A body or initializer must either fully constrain or place no constraints upon -a given instance of `impl Trait` in a type alias. +a given existential type. -The following is an example of an `impl Trait` type alias which contains -two instances of `impl Trait`. Each instance is determined by exactly one -of the functions: - -```rust -// The concrete type of `Baz` resolves to `(i32, &'static str)` -type Baz = (impl Default + Debug, impl Default + Debug); - -// This function places no constraints on the `impl Trait` types -fn new_baz() -> Baz { - (Default::default(), Default::default()) -} - -// This function fully constrains the first `impl Trait` type, -// but places no constraints upon the second. -fn add_to_first(baz: Baz) -> Baz { - let first: i32 = baz.0; - (first + 1, baz.1) -} - -// This function fully constrains the second `impl Trait` type, -// but places no constraints upon the first. -fn make_second_hello(baz: Baz) -> Baz { - (baz.0, "Hello, world!") -} -``` - -Outside of the module, `impl Trait` alias types behave the same way as -other `impl Trait` types, except that it can be assumed that two values with -the same `impl Trait` alias type are actually values of the same type: +Outside of the module, existential types behave the same way as +`impl Trait` types: their concrete type is hidden from the module. +However, it can be assumed that two values of the same existential type +are actually values of the same type: ```rust mod my_mod { - pub type Foo = impl Debug; + pub existential type Foo: Debug; pub fn foo() -> Foo { 5i32 } @@ -502,12 +475,12 @@ fn outside_mod() -> Foo { } ``` -One last difference between `impl Trait` aliases and normal type aliases is -that `impl Trait` aliases cannot be used in `impl` blocks: +One last difference between existential type aliases and normal type aliases is +that existential type aliases cannot be used in `impl` blocks: ```rust -type Foo = impl Debug; -impl Foo { // ERROR: `impl` cannot be used on `impl Trait` aliases +existential type Foo: Debug; +impl Foo { // ERROR: `impl` cannot be used on existential type aliases ... } impl MyTrait for Foo { // ERROR ^ @@ -521,8 +494,8 @@ of functions and traits on the underlying type? It seems like the answer should be "no" since doing so would give away the underlying type being hidden beneath the impl. Still, some version of this feature could be used eventually to implement traits or functions for closures, or -to express conditional bounds in `impl Trait` signatures -(e.g. `type Foo = impl Debug; impl Clone for Foo { ... }`). +to express conditional bounds in existential type signatures +(e.g. `existentail type Foo = impl Debug; impl Clone for Foo { ... }`). This is a complicated design space which has not yet been explored fully enough. In the future, such a feature could be added backwards-compatibly. @@ -543,54 +516,52 @@ cannot, such as `impl` blocks and `struct` definitions (i.e. `struct Foo { x: impl Trait }`). This inconsistency may be surprising to users. -Additionally, if Rust ever moves to a bare `Trait` (no `impl`) syntax, -`type Foo = impl Trait;` would likely require a new syntax, as -`type Foo = MyType;` and `type Foo = Trait;` have different-enough -behavior that the syntactic similarity would cause confusion. - # Alternatives [alternatives]: #alternatives We could instead expand `impl Trait` in a more focused but limited way, such as specifically extending `impl Trait` to work in traits without -allowing full "`impl Trait` type alias". +allowing full existential type aliases. A draft RFC for such a proposal can be seen [here](https://github.com/cramertj/impl-trait-goals/blob/impl-trait-in-traits/0000-impl-trait-in-traits.md). Any such feature could, in the future, be added as essentially syntax sugar on top of this RFC, which is strictly more expressive. The current RFC will also help us to gain experience with how people use -`impl Trait` in practice, allowing us to resolve some remaining questions +existential type aliases in practice, allowing us to resolve some remaining questions in the linked draft, specifically around how `impl Trait` associated types are used. Throughout the process we have considered a number of alternative syntaxes for -abstract types. There are a variety of small changes, similar to the proposal -here: +existential types. The syntax `existential type Foo: Trait;` is intended to be +a placeholder for a more concise and accessible syntax, such as +`abstract type Foo: Trait;`. A variety of variations on this theme have been +considered: - Instead of `abstract type`, it could be some single keyword like `abstype`. -- We could use a different keyword from `abstract`, like `opaque`. -- We could not include the `abstract` keyword at all, and just have a type -alias with only a bound and not not an `=` be "abstract." +- We could use a different keyword from `abstract`, like `opaque` or `exists`. +- We could omit a keyword altogether and use `type Foo: Trait;` syntax +(outside of trait definitions). -A more divergent alternative is not to have an "abstract type" feature at all, +A more divergent alternative is not to have an "existentail type" feature at all, but instead just have `impl Trait` be allowed in type alias position. -Everything written `abstract type $NAME: $BOUND;` in this RFC would instead be +Everything written `existential type $NAME: $BOUND;` in this RFC would instead be written `type $NAME = impl $BOUND;`. -The RFC settled on the abstract type syntax because we believe it will act as a -teaching aid. As a result of [RFC 1951][1951], `impl Trait` is sometimes +This RFC opted to avoid the `type Foo = impl Trait;` syntax because of its +potential teaching difficulties. +As a result of [RFC 1951][1951], `impl Trait` is sometimes universal quantiifcation and sometimes existential quantification. By providing a separate syntax for "explicit" existential quantification, `impl Trait` can -be taught as a syntactic sugar for generics and abstract types. By "just using -`impl Trait`," there would be no "bottom" explanation for what it means when it -is used as existential quantification. +be taught as a syntactic sugar for generics and existential types. By "just using +`impl Trait`" for named existential type declarations, +there would be no desugaring-based explanation for all forms of `impl Trait`. This choice has some disadvantages in comparison impl Trait in type aliases: - We introduce another new syntax on top of `impl Trait`, which inherently has some costs. - Users can't use it in a nested fashion without creating an addiitonal -abstract type. +existential type. Because of these downsides, we are open to reconsidering this question with more practical experience, and the final syntax is left as an unresolved @@ -599,9 +570,8 @@ question for the RFC. # Unresolved questions [unresolved]: #unresolved-questions -As discussed in the [alternatives][alternatives] section above, we intend to -reconsider whether `abstract type` is the optimal syntax before stabilizing -this feature. +As discussed in the [alternatives][alternatives] section above, we will need to +reconsider the optimal syntax before stabilizing this feature. Additionally, the following extensions should be considered in the future: From ed2b57aeb9a2cc12be16a1587e66a80709d018f1 Mon Sep 17 00:00:00 2001 From: boats Date: Mon, 18 Sep 2017 14:28:20 -0700 Subject: [PATCH 13/13] RFC 2071 is impl Trait and existential types --- ...mpl-trait-type-alias.md => 2071-impl-trait-type-alias.md} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename text/{0000-impl-trait-type-alias.md => 2071-impl-trait-type-alias.md} (98%) diff --git a/text/0000-impl-trait-type-alias.md b/text/2071-impl-trait-type-alias.md similarity index 98% rename from text/0000-impl-trait-type-alias.md rename to text/2071-impl-trait-type-alias.md index 64fe192ce28..5199f7e1e85 100644 --- a/text/0000-impl-trait-type-alias.md +++ b/text/2071-impl-trait-type-alias.md @@ -1,7 +1,8 @@ - Feature Name: impl-trait-existential-types - Start Date: 2017-07-20 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: https://github.com/rust-lang/rfcs/pull/2071 +- Rust Issue: https://github.com/rust-lang/rust/issues/44685 (existential types) +- Rust Issue: https://github.com/rust-lang/rust/issues/44686 (impl Trait in const/static/let) # Summary [summary]: #summary