Appendix B: Where can impl trait be used

Overview

Impl trait is accepted in the following locations:

PositionWho determines the hidden typeRoleStatus
Argument positionEach callerInputstable
Type aliasCode within the enclosing moduleOutputnightly
Return position, free fnsThe function bodyOutputstable
Return position, inherent implsThe function bodyOutputstable
Return position, trait implsThe function bodyOutputplanning rfc
Return position, traitsThe implInputplanning rfc
Let bindingThe enclosing function or code blockOutputaccepted rfc
Const bindingThe const initializerOutputaccepted rfc
Static bindingThe static initializerOutputaccepted rfc

Impl trait is not accepted in other locations; Appendix C covers some of the locations where impl Trait is not presently accepted and why.

Role: Input vs output

Impl traits in general play one of two roles.

Input impl trait

An input impl trait corresponds loosely to a generic parameter. The code that references the impl Trait may be instantiated multiple times with different values. For example, a function using impl Trait in argument position can be called with many different types for the impl Trait.

Output impl trait

An output impl trait plays a role similar to a type that is given by the user. In this case, the impl trait represents a single type (although that type may be relative to generic types are in scope) which is inferred from the code around the definition. For example, a free function with an impl Trait in return position will have the true return type inferred from the function body. The type represented by an output impl trait is called the hidden type. The code that is used to infer the value for an output impl trait is called its defining scope.

General rules for "input" vs "output"

In general, the role of impl Trait and '_ both follow the same rules in terms of being "input vs output". When in an argument listing, that is an "input" role and they correspond to a fresh parameter in the innermost binder. Otherwise, they are in "output" role and the corresponding to something which is inferred or selected from context (in the case of '_ in return position, it is selected based on the rules of lifetime elision; '_ within a function body corresponds to inference).

Type alias impl trait


#![allow(unused)]
fn main() {
type Foo = impl Trait;
}

Creates a opaque type whose value will be inferred by the contents of the enclosing module (and its submodules).

Fn argument position


#![allow(unused)]
fn main() {
fn foo(x: impl Trait)
}

becomes an "anonymous" generic parameter, analogous to


#![allow(unused)]
fn main() {
fn foo<T: Trait>(x: T)
}

However, when impl Trait is used on a function, the resulting type parameter cannot be specified using "turbofish" form; its value must be inferred. (status: this detail not yet decided).

Places this can be used:

  • Top-level functions and inherent methods
  • Trait methods
    • Implication: trait is not dyn safe

Fn return position


#![allow(unused)]
fn main() {
fn foo() -> impl Trait)
}

becomes an "anonymous" generic parameter, analogous to


#![allow(unused)]
fn main() {
fn foo<T: Trait>(x: T)

type Foo = impl Trait; // defining scope: just the fn
fn foo() -> Foo
}

Places this can be used:

  • Top-level functions and inherent methods
  • Trait methods (pending; no RFC)

Let bindings


#![allow(unused)]
fn main() {
fn foo() {
    let x: impl Debug = ...;
}
}

becomes a type alias impl trait, analogous to


#![allow(unused)]
fn main() {
type Foo = impl Debug; // scope: fn body
fn foo() {
    let x: Foo = ...;
}
}