Async fn in dyn trait
Welcome! This document explores how to combine dyn and impl Trait in return position. This is crucial pre-requisite for async functions in traits. As a motivating example, consider the trait AsyncIterator:
#![allow(unused)] fn main() { trait AsyncIterator { type Item; async fn next(&mut self) -> Option<Self::Item>; } }
The async fn here is, of course, short for a function that returns impl Future:
#![allow(unused)] fn main() { trait AsyncIterator { type Item; fn next(&mut self) -> impl Future<Output = Option<Self::Item>>; } }
The focus of this document is on how we can support dyn AsyncIterator. For an examination of why this is difficult, see this blog post.
Key details
Here is a high-level summary of the key details of our approach:
- Natural usage:
- To use dynamic dispatch, just write
&mut dyn AsyncIterator, same as any other trait. - Similarly, on the impl side, just write
impl AsyncIterator for MyType, same as any other trait.
- To use dynamic dispatch, just write
- Allocation by default, but not required:
- By default, trait functions that return
-> impl Traitwill allocate aBoxto store the trait, but only when invoked through adyn Trait(static dispatch is unchanged). - To support no-std or high performance scenarios, types can customize how an
-> impl Traitfunction is dispatch throughdyn. We show how to implement anInlineAsyncIteratortype, for example, that wraps anotherAsyncIteratorand stores the resulting futures on pre-allocated stack space.- rust-lang will publish a crate,
dyner, that provides several common strategies.
- rust-lang will publish a crate,
- By default, trait functions that return
- Separation of concerns:
- Users of a
dyn AsyncIteratordo not need to know (or care) whether the impl allocates a box or uses some other allocation strategy. - Similarly, authors of a type that implements
AsyncIteratorcan just write animpl. That code can be used with any number of allocation adapters.
- Users of a