Appendix B: Where can impl trait be used
Overview
Impl trait is accepted in the following locations:
Position | Who determines the hidden type | Role | Status |
---|---|---|---|
Argument position | Each caller | Input | |
Type alias | Code within the enclosing module | Output | |
Return position, free fns | The function body | Output | |
Return position, inherent impls | The function body | Output | |
Return position, trait impls | The function body | Output | |
Return position, traits | The impl | Input | |
Let binding | The enclosing function or code block | Output | |
Const binding | The const initializer | Output | |
Static binding | The static initializer | Output |
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 = ...; } }