2017-09-18 07:57:47 +08:00
- Feature Name: impl-trait-existential-types
2017-09-18 06:28:48 +08:00
- Start Date: 2017-07-20
2017-11-01 21:29:36 +08:00
- RFC PR: [rust-lang/rfcs#2071 ](https://github.com/rust-lang/rfcs/pull/2071 )
- Rust Issue: [rust-lang/rust#44685 ](https://github.com/rust-lang/rust/issues/44685 ) (existential types)
- Rust Issue: [rust-lang/rust#44686 ](https://github.com/rust-lang/rust/issues/44686 ) (impl Trait in const/static/let)
2017-07-21 00:24:13 +08:00
# Summary
[summary]: #summary
2017-09-18 07:57:47 +08:00
Add the ability to create named existential types and
support `impl Trait` in `let` , `const` , and `static` declarations.
2017-07-21 00:24:13 +08:00
```rust
2017-09-18 07:57:47 +08:00
// existential types
existential type Adder: Fn(usize) -> usize;
2017-07-21 00:24:13 +08:00
fn adder(a: usize) -> Adder {
|b| a + b
}
2017-09-18 07:57:47 +08:00
// existential type in associated type position:
2017-07-21 00:24:13 +08:00
struct MyType;
impl Iterator for MyType {
2017-09-18 07:57:47 +08:00
existential type Item: Debug;
2017-07-21 00:24:13 +08:00
fn next(& mut self) -> Option< Self::Item > {
Some("Another item!")
}
}
// `impl Trait` in `let` , `const` , and `static` :
const ADD_ONE: impl Fn(usize) -> usize = |x| x + 1;
static MAYBE_PRINT: Option< impl Fn ( usize ) > = Some(|x| println!("{}", x));
fn my_func() {
let iter: impl Iterator< Item = i32 > = (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
2017-09-18 07:57:47 +08:00
it possible to create a named existential type:
2017-07-21 00:24:13 +08:00
```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.
2017-09-18 07:57:47 +08:00
existential type Item: Debug;
2017-07-21 00:24:13 +08:00
fn next(& mut self) -> Option< Self::Item > {
Some("hello")
}
}
```
2017-09-18 07:57:47 +08:00
This syntax allows us to declare multiple items which refer to
the same existential type:
2017-07-21 00:24:13 +08:00
```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).
2017-09-18 07:57:47 +08:00
pub existential type Foo: Debug;
2017-07-21 00:24:13 +08:00
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< Item = i32 > = (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);
```
2017-08-05 12:45:03 +08:00
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.
2017-07-21 00:24:13 +08:00
In our example above, this means that, while we can "display" the
2017-08-05 12:45:03 +08:00
value of `displayable` , the concrete type `&str` is hidden:
2017-07-21 00:24:13 +08:00
```rust
use std::fmt::Display;
// Without `impl Trait` :
2017-08-05 12:45:03 +08:00
const DISPLAYABLE: & str = "Hello, world!";
fn display() {
println!("{}", DISPLAYABLE);
assert_eq!(DISPLAYABLE.len(), 5);
2017-07-21 00:24:13 +08:00
}
// With `impl Trait` :
2017-08-05 12:45:03 +08:00
const DISPLAYABLE: impl Display = "Hello, world!";
2017-07-21 00:24:13 +08:00
2017-08-05 12:45:03 +08:00
fn display() {
// We know `DISPLAYABLE` implements `Display` .
println!("{}", DISPLAYABLE);
2017-07-21 00:24:13 +08:00
// ERROR: no method `len` on `impl Display`
2017-08-05 12:45:03 +08:00
// 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);
2017-07-21 00:24:13 +08:00
}
```
`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;
```
2017-08-05 12:45:03 +08:00
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<std::iter::Map<std::ops::Range<{integer}>, [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< Item = i32 > = (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();
```
2017-09-18 07:57:47 +08:00
## Guide: Existential types
[guide-existential]: #guide -existential
2017-07-21 00:24:13 +08:00
2017-09-18 07:57:47 +08:00
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.
2017-07-21 00:24:13 +08:00
```rust
use std::fmt::Debug;
2017-09-18 07:57:47 +08:00
existential type Foo: Debug;
2017-07-21 00:24:13 +08:00
fn foo() -> Foo {
5i32
}
```
2017-09-18 07:57:47 +08:00
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
2017-11-01 05:59:44 +08:00
hidden outside of the module. Outside the module, the only thing that
2017-09-18 07:57:47 +08:00
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:
2017-07-21 00:24:13 +08:00
```rust
use std::fmt::Debug;
mod my_mod {
2017-09-18 07:57:47 +08:00
pub existential type Foo: Debug;
2017-07-21 00:24:13 +08:00
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() {
2017-09-18 06:28:48 +08:00
// Creates a variable `x` of type `Foo` , which is only known to implement `Debug`
2017-07-21 00:24:13 +08:00
let x = my_mod::foo();
2017-09-18 06:28:48 +08:00
// Because we're outside `my_mod` , the user cannot determine the type of `Foo` .
2017-09-18 07:57:47 +08:00
let y: i32 = my_mod::foo(); // ERROR: expected type `i32` , found existential type `Foo`
2017-09-18 06:28:48 +08:00
// However, the user can use its `Debug` impl:
println!("{:?}", x);
2017-07-21 00:24:13 +08:00
}
```
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
2017-09-18 07:57:47 +08:00
existential type, like in `let x: i32 = foo();` above. This aids the function's
2017-09-18 06:28:48 +08:00
ability to locally infer the concrete type of `Foo` .
2017-07-21 00:24:13 +08:00
2017-09-18 07:57:47 +08:00
One particularly noteworthy use of existential types is in trait
2017-07-21 00:24:13 +08:00
implementations.
2017-09-18 07:57:47 +08:00
With this feature, we can declare associated types as follows:
2017-07-21 00:24:13 +08:00
```rust
struct MyType;
impl Iterator for MyType {
2017-09-18 07:57:47 +08:00
existential type Item: Debug;
2017-07-21 00:24:13 +08:00
fn next(& mut self) -> Option< Self::Item > {
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 {
2017-09-18 07:57:47 +08:00
existential type Item: Fn(i32) -> i32;
2017-07-21 00:24:13 +08:00
fn next(& mut self) -> Option< Self::Item > {
Some(|x| x + 5)
}
}
```
2017-09-18 07:57:47 +08:00
Existential types can also be used to reference unnameable types in a struct
2017-07-21 00:24:13 +08:00
definition:
```rust
2017-09-18 07:57:47 +08:00
existential type Foo: Debug;
2017-07-21 00:24:13 +08:00
fn foo() -> Foo { 5i32 }
struct ContainsFoo {
some_foo: Foo
}
```
2017-08-25 15:44:05 +08:00
2017-09-18 07:57:47 +08:00
It's also possible to write generic existential types:
2017-07-21 00:24:13 +08:00
```rust
#[derive(Debug)]
struct MyStruct< T: Debug > {
inner: T
};
2017-09-18 07:57:47 +08:00
existential type Foo< T > : Debug;
2017-07-21 00:24:13 +08:00
fn get_foo< T: Debug > (x: T) -> Foo< T > {
MyStruct {
inner: x
}
}
```
2017-09-18 06:28:48 +08:00
Similarly to `impl Trait` under
2017-08-25 15:44:05 +08:00
[RFC 1951 ](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md ),
2017-09-18 07:57:47 +08:00
`existential type` implicitly captures all generic type parameters in scope. In
practice, this means that existential associated types may contain generic
2017-09-18 06:28:48 +08:00
parameters from their impl:
2017-08-05 15:35:18 +08:00
```rust
2017-08-25 15:44:05 +08:00
struct MyStruct;
trait Foo< T > {
type Bar;
fn bar() -> Bar;
2017-08-05 15:35:18 +08:00
}
2017-08-25 15:44:05 +08:00
impl< T > Foo< T > for MyStruct {
2017-09-18 07:57:47 +08:00
existentail type Bar: Trait;
2017-08-31 01:18:45 +08:00
fn bar() -> Self::Bar {
2017-08-25 15:44:05 +08:00
...
// Returns some type MyBar< T >
}
2017-08-05 15:35:18 +08:00
}
```
2017-09-18 06:28:48 +08:00
However, as in 1951, lifetime parameters must be explicitly annotated.
2017-08-25 15:44:05 +08:00
2017-07-21 00:24:13 +08:00
# 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.
2017-09-18 07:57:47 +08:00
## Reference: Existential Types
[reference-existential]: #reference -existential
2017-07-21 00:24:13 +08:00
2017-09-18 07:57:47 +08:00
Existential types are similar to normal type aliases, except that their
2017-08-25 15:44:05 +08:00
concrete type is determined from the scope in which they are defined
(usually a module or a trait impl).
2017-07-21 00:24:13 +08:00
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
2017-09-18 07:57:47 +08:00
existential type Foo = impl Debug;
2017-07-21 00:24:13 +08:00
fn foo() -> Foo {
5i32
}
```
2017-08-07 14:46:30 +08:00
`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` :
2017-07-21 00:24:13 +08:00
```rust
2017-08-07 14:46:30 +08:00
fn add_to_foo_1(x: Foo) {
2017-09-18 07:57:47 +08:00
x + 1 // ERROR: binary operation `+` cannot be applied to existential type `Foo`
// ^ `x` here is type `Foo` .
2017-08-07 14:46:30 +08:00
// Type annotations needed to resolve the concrete type of `x` .
// (^ This particular error should only appear within the module in which
// `Foo` is defined)
2017-07-21 00:24:13 +08:00
}
2017-08-07 14:46:30 +08:00
fn add_to_foo_2(x: Foo) {
let x: i32 = x;
x + 1
2017-07-21 00:24:13 +08:00
}
2017-08-25 15:44:05 +08:00
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
}
2017-07-21 00:24:13 +08:00
```
2017-09-18 07:57:47 +08:00
Each existential type declaration must be constrained by at least
2017-08-08 01:54:57 +08:00
one function body or const/static initializer.
A body or initializer must either fully constrain or place no constraints upon
2017-09-18 07:57:47 +08:00
a given existential type.
2017-07-21 00:24:13 +08:00
2017-09-18 07:57:47 +08:00
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:
2017-07-21 00:24:13 +08:00
```rust
mod my_mod {
2017-09-18 07:57:47 +08:00
pub existential type Foo: Debug;
2017-07-21 00:24:13 +08:00
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())
}
}
```
2017-09-18 07:57:47 +08:00
One last difference between existential type aliases and normal type aliases is
that existential type aliases cannot be used in `impl` blocks:
2017-07-21 00:24:13 +08:00
```rust
2017-09-18 07:57:47 +08:00
existential type Foo: Debug;
impl Foo { // ERROR: `impl` cannot be used on existential type aliases
2017-07-21 00:24:13 +08:00
...
}
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
2017-09-18 07:57:47 +08:00
to express conditional bounds in existential type signatures
(e.g. `existentail type Foo<T> = impl Debug; impl<T: Clone> Clone for Foo<T> { ... }` ).
2017-07-21 00:24:13 +08:00
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.
2017-08-31 01:18:45 +08:00
There are potentially simpler ways to achieve some of the goals of this RFC,
2017-07-21 00:24:13 +08:00
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.
# 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
2017-09-18 07:57:47 +08:00
allowing full existential type aliases.
2017-07-21 00:24:13 +08:00
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
2017-09-18 07:57:47 +08:00
existential type aliases in practice, allowing us to resolve some remaining questions
2017-07-21 00:24:13 +08:00
in the linked draft, specifically around how `impl Trait` associated types
are used.
2017-09-18 06:28:48 +08:00
Throughout the process we have considered a number of alternative syntaxes for
2017-09-18 07:57:47 +08:00
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:
2017-09-18 06:28:48 +08:00
- Instead of `abstract type` , it could be some single keyword like `abstype` .
2017-09-18 07:57:47 +08:00
- 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).
2017-09-18 06:28:48 +08:00
2017-09-18 07:57:47 +08:00
A more divergent alternative is not to have an "existentail type" feature at all,
2017-09-18 06:28:48 +08:00
but instead just have `impl Trait` be allowed in type alias position.
2017-09-18 07:57:47 +08:00
Everything written `existential type $NAME: $BOUND;` in this RFC would instead be
2017-09-18 06:28:48 +08:00
written `type $NAME = impl $BOUND;` .
2017-09-18 07:57:47 +08:00
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
2017-09-18 06:28:48 +08:00
universal quantiifcation and sometimes existential quantification. By providing
a separate syntax for "explicit" existential quantification, `impl Trait` can
2017-09-18 07:57:47 +08:00
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` .
2017-09-18 06:28:48 +08:00
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
2017-09-18 07:57:47 +08:00
existential type.
2017-09-18 06:28:48 +08:00
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.
2017-08-05 15:35:18 +08:00
2017-07-21 00:24:13 +08:00
# Unresolved questions
[unresolved]: #unresolved -questions
2017-09-18 07:57:47 +08:00
As discussed in the [alternatives][alternatives] section above, we will need to
reconsider the optimal syntax before stabilizing this feature.
2017-09-18 06:28:48 +08:00
Additionally, the following extensions should be considered in the future:
2017-07-21 00:24:13 +08:00
- 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<T>(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.
2017-08-07 14:46:30 +08:00
- 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.