This is the first blog post as part of a series of blog posts that will try to explain how impl trait in return position inside and outside traits and async fns in traits works. This first blog post summarizes the concepts with some simple examples. In the following ones we would be explaining a bit about the internal details.
What is an RPIT?
RPIT stands for Return Position Impl Trait. It’s an opaque return type, so a type whose concrete data structure is not defined in an interface, exposing as the interface that it just implements the mentioned trait.
fn odd_integers(start: u32, stop: u32) -> impl Iterator<Item = u32> {
(start..stop).filter(|i| i % 2 != 0)
}
In this example, the RPIT, it’s an opaque type defined as impl Iterator<Item = u32>
. Basically, a type that implements Iterator
whose items are u32
. Although the function internally has a concrete
type, it is exposed as an opaque type for consumers. So all the callers
know is that it implements Iterator<Item=u32>
.
To learn more about RPITs, check out the RFC.
What is an RPITIT?
RPITIT stands for Return Position Impl Trait In Trait. It’s basically an
RPIT as defined above but defined for a function that lives inside a
trait. Note that this feature has not been stabilized yet. In order to
use it, you would need to use
#![feature(return_position_impl_trait_in_trait)]
feature flag.
trait NewIntoIterator {
type Item;
fn into_iter(self) -> impl Iterator<Item = Self::Item>;
}
In this example, impl Iterator<Item=Self::Item>
is an RPIT returned by
into_iter
and it’s also an RPITIT because it lives inside a trait, in
this case NewIntoIterator
.
On the implementation side as the return type, we can use or a concrete
type that implements Iterator
or just have the impl Iterator
RPIT
again:
// impl example 1:
impl<T> NewIntoIterator for Vec<T> {
type Item = T;
// can return a concrete type `IntoIter<T>`
fn into_iter(&self) -> IntoIter<T> {
...
}
}
// impl example 2:
impl NewIntoIterator for MyCollection<T> {
type Item = T;
// can return an RPIT `impl Iterator<Item = Self::Item>`
fn into_iter(self) -> impl Iterator<Item = Self::Item> {
...
}
}
To learn more about RPITIT, check the draft RFC.
What is an AFIT?
An async function’s return type is desugared into impl Future<Output = return_type_of_the_fn>
, which we have previously discussed that it’s an
RPIT. An AFIT stands for Async Fn In Trait, so an AFIT’s return type
would be an RPIT inside a trait, so an RPITIT. Note that this feature
has not been stabilized yet. In order to use it, you would need to use
#![feature(return_position_impl_trait_in_trait)]
feature flag.
trait Service {
async fn request(&self, key: i32) -> Response;
}
is syntactic sugar for:
trait Service {
fn request(&self, key: i32) -> impl Future<Output=Response>;
}
In this example, we originally have an AFIT (async function in trait),
which gets desugared into impl Future<Output=Response>
RPITIT.
At this point the connection between the three concepts should be clear. An AFIT is a particular kind of RPITIT, so there’s no AFITs without RPITITs and an RPITIT is a particular kind of RPIT, so there’s no RPITITs without RPITs either.
To learn more about AFIT check out the static async fn in trait RFC.
How RPITs work today?
Given the following function that returns an RPIT:
fn foo<'a>(&'a self) -> impl Debug + 'a { ... }
when we do AST->HIR lowering we get:
fn foo<'a>(&'a self) -> Foo<'static, 'a> {
type Foo<'b, 'a1>: Debug + 'a1;
...
}
Both the new lifetime 'a1
and the use of 'static
may be very
surprising to you. This is basically the topic for our following blog
post :). Stay tuned.