Stabilization Report: Minimal Type Alias Impl Trait

  • Relevant RFCs:
  • Relevant tracking issue(s):
  • Implementation history: too hard to track

To be stabilized

Type alias impl Trait (TAIT) refers to impl Trait appearing in a type alias. impl Trait in this position can appear as the entire value of the type alias:

type Alias1 = impl Trait;

Desugarings and extended forms

Most of this report is concerned only with the 'simple' TAIT described in the previous form, and thus written as if each TAIT has a name. However, we do support some extended forms, which are effectively desugarings. These extended forms are described here.

Non-trivial type aliases

impl Trait does not have to appear at the "top level" of a type alias. It may appear embedded within other types:

type Alias2 = (impl Trait, impl Trait);

One can think of the latter case (Alias2) as a kind of shorthand that expands to multiple named aliases:

type Temp0 = impl Trait; type Temp1 = impl Trait; type Alias2 = (Temp0, Temp1);

FIXME: should maybe be a bit more specific here; for example, this doesn't work (should it?):

type Z = fn() -> impl Sized; fn bar() -> Z { || 0u32 }

In an impl

Impl Trait can also appear in an impl:

impl Service for MyType { type Future = impl Future<Output = Response>; fn process_request(&self) -> Self::Future { async move { ... } } }

This can effectively be desugared into a TAIT whose scope encompasses just the impl. Roughly like the following:

FIXME: double check this desugaring is correct

mod _hidden_module { use super::*; type Future = impl Future<Output = Response>; impl Service for MyType { fn process_request(&self) -> Future { async move { ... } } } }

Semantics of a type alias impl Trait as discussed in the RFC

A TAIT conceptually creates an "opaque type" whose underlying concrete type is inferred by the compiler. The inference is done based on the other items within the same module as the impl Trait (transitively).

Example:

mod m { pub type MyFuture = impl Future<Output = u32>; pub fn foo() -> MyFuture { async move { 22 } } }

References to a type alias impl trait from outside the module treat it like an "opaque alias" (i.e., some type that implements future). Note that it has identity, even if we don't know precisely what team it is:

mod m { .. /* as above */ .. } pub fn bar() -> m::MyFuture { foo() // OK }

Type alias impl Trait requires uses within the module where it is defined. You cannot have a type alias impl trait that is defined by uses outside of the module:

mod m { pub type MyFuture = impl Future; // Error, no uses from outside } pub fn bar() -> m::MyFuture { foo() // OK }

Semantics that are up for stabilization ("minimal type alias impl Trait")

The "minimal type alias impl Trait" (MTAIT) up for stabilization today limits the set of places where a TAIT T is used. To be accepted, a TAIT T defined in the module m must be used only in the following places:

  • outside of the module m (in which case, T is used as an opaque alias)
  • "defining uses" within the module m:
    • in the return type of a function
    • as the value for an associated type

Higher-order pattern matching, not unification

XXX

Places where the compiler's behavior today differs from the RFC (not eligible for standardization)

The RFC permits TAIT to be used in many locations, most of which are still considered unstable. This is partly because the compiler's inference algorithm is not yet able to handle the full complexity described by the RFC. Here are some examples that remain unstable:

type MyFuture = impl Future; pub fn foo(x: MyFuture) { // Illegal: cannot use MyFuture in an argument } pub fn foo(x: MyFuture) -> MyFuture { // Illegal: cannot use MyFuture in an argument x } struct Foo { x: MyFuture // Illegal: cannot use MyFuture in a field type }

FIXME(Jack): maybe add a comment about submodule workaround for using type in struct field

Applications of this subset

This can be used to define traits that return futures, although to truly handle the use case in full one also needs generic associated types. But MTAIT can be used for the tower Service trait, for example.

Test cases

  • Defining use is in a submodule
  • Defining use that doesn't meet the required bounds
  • Use of impl Trait in an impl as value of an associated type
    • impl Service { type Future = impl Future; fn foo() -> Self::Future { ... } }
  • Desugaring:
    • Principle: any place that the value of the impl Trait is uniquely determined by the value of Foo
    • use of impl Trait in a tuple
      • type Foo = (impl Trait, u32)
    • use of impl Trait in a struct argument
      • type Foo = Vec<impl Trait>
    • use of impl Trait in an impl as the value for an associated type in a dyn
      • type Foo = Box<dyn Iterator<Item = impl Debug>>
    • use of impl Trait in an impl as the value for an associated type in an impl trait
      • type Foo = impl Iterator<Item = impl Debug>
      • type Bar = impl Debug; type Foo = impl Iterator<Item = Bar>
    • use in a fn type type Foo = fn(impl Trait)
      • type Foo = fn(impl Debug)
  • Incomplete inference for the type (some parts unspecified)
  • Disagreement between fns in types
  • Disagreement between fns in the lifetimes
  • Use outside of a "defining use"
    • type of a let
    • argument types
    • field types
    • static/const type ?
    • TODO input type in impl Trait (knowing the self type doesn't tell us the other type parameters)
  • Defining use sites:
    • value of an associated type
      • type Foo = impl Trait; impl Foo { type Item = Foo }
    • function return type
      • type Foo = impl Trait; fn foo() -> Foo { }
  • Inference cycle
  • Auto trait leakage
  • Weird return types:
    • -> impl Future<Output = N>
    • -> impl Trait<N>
    • fn(N)

Logical definition/Chalk

FIXME

  • Current tracking issue for Chalk #335
    • Outstanding issue there is WF/implied bounds
    • Followup with hidden type "isn't known"?
Select a repo