owned this note
owned this note
Published
Linked with GitHub
---
tags: rust, tait
---
# type alias impl trait
TAIT: type alias impl trait
* [syntax RFC](https://github.com/rust-lang/rfcs/pull/2515)
* [logic RFC](https://github.com/rust-lang/rfcs/pull/2071)
* [Tracking issue](https://github.com/rust-lang/rust/issues/63063)
* [Project board](https://github.com/orgs/rust-lang/projects/22/views/1)
* [Dedicated Tests](https://github.com/rust-lang/rust/tree/master/src/test/ui/type-alias-impl-trait)
* [Open issues](https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3AF-type_alias_impl_trait+-label%3AE-needs-test)
### What is type-alias-impl-trait?
type-alias-impl-trait allows moving the already stable `impl Trait`s (that are only legal in function return types) into type aliases and thus be able to use them in more places and multiple times.
```rust
fn foo() -> impl Trait {
value_of_type_that_implements_Trait
}
// The above function can be changed to the
// following without a change in behaviour for callers.
type Foo = impl Trait;
fn foo() -> Foo {
value_of_type_that_implements_Trait
}
```
In contrast to return-position-impl-trait, this type alias can be used as the return type of multiple functions, but all functions doing so must use the same hidden type. This is similar to how all code paths in a function with a return-position-impl-trait must return the same type:
```rust
fn foo() -> impl Debug {
if true {
return 42;
}
"42" // ERROR: another return site returns an `i32`
}
type Bar = impl Debug;
fn bar() -> Bar {
42
}
fn boo() -> Bar {
"42" // ERROR: another function uses `i32` for `Bar`
}
```
In contrast to return-position-impl-trait, these type aliases can also be used in other locations.
#### Argument position
Without knowing the hidden type, we can still use the opaque type and use its trait bounds. In this case we can use the `Debug` trait to render it:
```rust
fn bop(x: Bar) {
println!("{x:?}");
}
```
Binding a hidden type works in both directions, not just assigning a hidden type value to the opaque type, but also reading an opaque type into a hidden type value:
```rust
fn bup(x: Bar) {
let x: i32 = x;
}
```
This does not "reveal" the hidden type. It binds an explicitly known `i32` type as the hidden type of `Bar` and will error if that's not the hidden type everywhere else, too.
As a last usage, you can avoid binding any hidden types and just use the type-alias-impl-trait by just forwarding it elsewhere:
```rust
fn burp(x: Bar) -> Bar {
x
}
```
#### Binding types
You can also use type-alias-impl-trait for the type
of local variables, constants, statics, ...
```rust
let x: Bar = 42;
const X: Bar = 42;
static Y: Bar = 42;
```
#### Nested in other types
You can use type-alias-impl-trait in other types:
```rust
struct MyStruct {
bar: Bar,
}
```
and use it just like other uses of `Bar`:
```rust
fn foo(my_struct: &MyStruct) {
println!("{:?}", my_struct.bar);
}
fn new() -> MyStruct {
MyStruct {
bar: 42
}
}
```
#### Usage in trait impls
Since type-alias-impl-trait can be referenced anywhere a type alias could be, this also means you can use them in `impl` blocks:
```rust
type Foo = impl Trait;
impl Bar for Foo {}
```
There's a huge caveat though: now it's possible for there to be an impl for an opaque type *and* its hidden type:
```rust
type Foo = impl Trait;
fn foo() -> Foo {}
impl Bar for Foo {}
impl Bar for () {} // ERROR conflicts with `impl Bar for Foo`
```
This check is *not* done by revealing the hidden type, but by checking whether a type could be a hidden type for that specific opaque type. So the following program is legal:
```rust
trait Trait {}
impl Trait for () {}
type Foo = impl Trait;
fn foo() -> Foo {}
impl Bar for Foo {}
impl Bar for i32 {}
```
This is legal, because `i32` could not possibly be a hidden type of `Foo`, because it doesn't implement `Trait` wich is a requirement for all hypothetical hidden types of `Foo`.
This is tested very thoroughly and is actually the simplest sound implementation for opaque types in coherence. While we could be more restrictive (just outright forbidding opaque types), that's not actually simpler from a compiler perspective and it's a neat kind of feature to support, even if we don't know the use case yet.
#### Associated types
Associated types can also be type-alias-impl-trait (associated-type-impl-trait?):
```rust
impl Deref for MyType {
type Target = impl Trait;
fn deref(&self) -> &Self::Target {
&self.field
}
}
```
While this example is fairly artificial, the real benefit is when you have unnameable types like `async` blocks:
```rust
impl IntoFuture for MyType {
type Output = ();
type Future = impl Future<Output = ()>;
fn into_future(self) -> Self::Future {
async move {
// do stuff here
}
}
}
```
This way you do not need to write burdensome `Future` impls yourself. Similarly with complex `Iterator` implementations.
#### Defining scope
Similar to return-position-impl-trait, you can only bind a hidden type of a type-alias-impl-trait within a specific "scope" (henceforth called "defining scope"). The defining scope of a return-position-impl-trait is the function's body, excluding other items nested within that function's body (we may want to relax that restriction on return-position-impl-trait in the future).
The defining scope of a type-alias-impl-trait is the scope in which it was defined. So usually a module and all its child items, but it can also be a function body, const initializer and similar scopes that can define items.
Any use of the type-alias-impl-trait within the defining scope will become a **defining use** (meaning it binds a hidden type), if the type is coerced to or from, equated with, or subtyped with any other concrete type. Usages that rely solely on the trait bounds of the type are not considered defining. Similarly, usages that just pass a value of a type-alias-impl-trait around into other places of the type-alias-impl-trait type are not considered defining.
### Papercuts:
* cycle errors around auto-traits https://github.com/rust-lang/rust/issues/55997 (if there are non-defining uses within the defining use module, moving the non-defining uses up in the module tree makes everything compile)