Skip to content

Commit 57e6c56

Browse files
authored
Address feedback and revise
1 parent 1f32339 commit 57e6c56

File tree

1 file changed

+58
-41
lines changed

1 file changed

+58
-41
lines changed

posts/inside-rust/2022-11-02-async-fn-in-trait-nightly.md renamed to posts/inside-rust/2022-11-17-async-fn-in-trait-nightly.md

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,47 @@ author: Tyler Mandry
55
team: The Rust Async Working Group <https://www.rust-lang.org/governance/wgs/wg-async>
66
---
77

8-
`async` and `.await` were a major improvement in the ergonomics of writing async code in Rust. If you use async Rust for long, however, you'll surely discover a big limitation: `async fn` doesn't work in traits.
8+
The async working group is excited to announce that `async fn` can now be used in traits in the nightly compiler. You can now write code like this:
99

10-
Traits are Rust's mechanism for writing generic code. How, you might reasonably ask, could we ship a feature that doesn't work in *traits*? As we'll see, this is a big pain point for users, but there are workarounds available. Let's go over the state of things today and then talk about what new features are available in the nightly compiler.
10+
```rust
11+
#![feature(async_fn_in_trait)]
12+
13+
trait Database {
14+
async fn fetch_data(&self) -> String;
15+
}
16+
17+
impl Database for MyDb {
18+
async fn fetch_data(&self) -> String { ... }
19+
}
20+
```
21+
22+
A full working example is available in the [playground][play-concrete-spawn]. There are some limitations we'll cover, as well as a few known bugs to be worked out, but we think it is ready for some users try. Read on for the specifics.
1123

12-
## Recap: The problem of `async fn` in trait
24+
## Recap: How async/await works in Rust
1325

14-
An `async fn` returns a `Future`, which is some object that represents an ongoing asynchronous computation.
26+
`async` and `.await` were a major improvement in the ergonomics of writing async code in Rust. In Rust, an `async fn` returns a `Future`, which is some object that represents an ongoing asynchronous computation.
1527

16-
However, the type of the future does not appear in the signature of an `async fn`. When you write an async function like this:
28+
The type of the future does not actually appear in the signature of an `async fn`. When you write an async function like this:
1729

1830
```rust
19-
impl MyDatabase {
20-
async fn fetch_data(&self) -> String { ... }
21-
}
31+
async fn fetch_data(db: &MyDb) -> String { ... }
2232
```
2333

2434
The compiler rewrites it to something like this:
2535

2636
```rust
27-
impl MyDatabase {
28-
fn fetch_data<'a>(&'a self) -> impl Future<Output = String> + 'a {
29-
async move { ... }
30-
}
37+
fn fetch_data<'a>(db: &'a MyDb) -> impl Future<Output = String> + 'a {
38+
async move { ... }
3139
}
3240
```
3341

34-
The `impl Future` here is some _opaque type_ that implements `Future`. It must be opaque because the type is generated by the compiler and doesn't have a name. That type is *specific to the async block*.
42+
This "desugared" signature is something you can write yourself, and it's useful for examining what goes on under the hood. The `impl Future` syntax here represents some _opaque type_ that implements `Future`.
43+
44+
The future is a state machine responsible for knowing how to continue making progress the next time it wakes up. When you write code in an `async` block, the compiler generates a future type specific to that async block for you. This future type does not have a name, so we must instead use an opaque type in the function signature.
45+
46+
## The historic problem of `async fn` in trait
3547

36-
If you wanted to do this in a trait, you'd be stuck because traits don't support returning opaque types. You might instead try to write it using an associated type:
48+
Traits are the fundamental mechanism of abstraction in Rust. So what happens if you want to put an async method in a trait? Each `async` block or function creates a unique type, so you would want to express that each implementation can have a different Future for the return type. Thankfully, we have associated types for this:
3749

3850
```rust
3951
trait Database {
@@ -42,26 +54,26 @@ trait Database {
4254
}
4355
```
4456

45-
Notice that this associated type is generic. That hasn't been supported in the language [until now][GATs]. Unfortunately, even with GATs, you can't write an implementation that uses `async`:
57+
Notice that this associated type is generic. Generic associated types haven't been supported in the language... [until now][GATs]! Unfortunately though, even with GATs, you still can't write a trait _implementation_ that uses `async`:
4658

4759
```rust
48-
impl Database for MyDatabase {
49-
type FetchData<'a> = /* what goes here??? */;
60+
impl Database for MyDb {
61+
type FetchData<'a> = /* what type goes here??? */;
5062
fn fetch_data(&self) -> FetchData<'a> { async move { ... } }
5163
}
5264
```
5365

54-
Since you can't name the type constructed by an async block, the only option is to use an opaque type. But those are not supported in type aliases, including associated types![^tait]
66+
Since you can't name the type constructed by an async block, the only option is to use an opaque type (the `impl Future` we saw earlier). But those are not supported in associated types![^tait]
5567

5668
[^tait]: This feature is called ["type alias impl trait"](https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html).
5769

58-
### Workarounds available today
70+
### Workarounds available in the stable compiler
5971

60-
So we need a concrete type to specify in our impl, if not our trait. There are a couple ways of achieving this today.
72+
So to write an `async fn` in a trait we need a concrete type to specify in our impl, if not our trait. There are a couple ways of achieving this today.
6173

6274
#### Runtime type erasure
6375

64-
We can avoid writing the future type by erasing it with `dyn`. Taking our example from above, you would write your trait like this:
76+
First, we can avoid writing the future type by erasing it with `dyn`. Taking our example from above, you would write your trait like this:
6577

6678
```rust
6779
trait Database {
@@ -70,7 +82,7 @@ trait Database {
7082
}
7183
```
7284

73-
This is significantly more verbose (and it gets worse for implementers), but it achieves the goal of combining async with traits. What's more, the [async-trait] proc macro crate rewrites your code for you, allowing you to simply write
85+
This is significantly more verbose, but it achieves the goal of combining async with traits. What's more, the [async-trait] proc macro crate rewrites your code for you, allowing you to simply write
7486

7587
```rust
7688
#[async_trait]
@@ -79,18 +91,24 @@ trait Database {
7991
}
8092

8193
#[async_trait]
82-
impl Database for MyDatabase {
94+
impl Database for MyDb {
8395
async fn fetch_data(&self) -> String { ... }
8496
}
8597
```
8698

87-
This is an excellent solution for the people who can use it! Unfortunately, not everyone can. It doesn't work in `no_std` contexts. Dynamic dispatch and allocation come with overhead that can be [crippling][barbara-benchmark] to highly performance-sensitive code. Finally, it bakes a lot of assumptions into the trait itself: allocation with `Box`, dynamic dispatch, and the `Send`-ness of the futures. This makes it unsuitable for many libraries.
99+
This is an excellent solution for the people who can use it!
88100

89-
So these workarounds leave something to be desired. Besides, users [expect][alan-async-traits] to be able to write `async fn` in traits, and the experience of adding an external crate dependency is a papercut that gives async Rust a reputation for being difficult to use.
101+
Unfortunately, not everyone can. You can't use `Box` in no_std contexts. Dynamic dispatch and allocation come with overhead that can be [crippling][barbara-benchmark] to highly performance-sensitive code. Finally, it bakes a lot of assumptions into the trait itself: allocation with `Box`, dynamic dispatch, and the `Send`-ness of the futures. This makes it unsuitable for many libraries.
102+
103+
Besides, users [expect][alan-async-traits] to be able to write `async fn` in traits, and the experience of adding an external crate dependency is a papercut that gives async Rust a reputation for being difficult to use.
90104

91105
#### Manual `poll` implementations
92106

93-
Traits that need to work with zero overhead or in no_std contexts had another option: they could build polling directly into their interface. For example, the `Stream` trait in the `futures` crate:
107+
Traits that need to work with zero overhead or in no_std contexts have another option: they can take the concept of polling from the [`Future` trait](https://doc.rust-lang.org/stable/std/future/trait.Future.html) and build it directly into their interface. The `Future::poll` method returns `Poll::Ready(Output)` if the future is complete and `Poll::Pending` if the future is waiting on some other event.
108+
109+
You can see this pattern, for example, in the Stream[^stream-futures] trait. The signature of `Stream::poll_next` is a cross between `Future::poll` and `Iterator::next`.
110+
111+
[^stream-futures]: The [`Stream` trait](https://docs.rs/futures/latest/futures/stream/trait.Stream.html) is part of the `futures` crate, not the standard library.
94112

95113
```rust
96114
pub trait Stream {
@@ -103,13 +121,11 @@ pub trait Stream {
103121
}
104122
```
105123

106-
This has the same signature as [`Future::poll`](https://doc.rust-lang.org/stable/std/future/trait.Future.html), a method that returns either `Poll::Ready(Output)` if the future is complete, or `Poll::Pending` if it is waiting on some other event. Effectively, a trait implementing `Stream` is like a special Future that you could continue to poll for the next item after receiving `Poll::Ready`.
107-
108124
Before async/await, it was very common to write manual `poll` implementations. Unfortunately, they proved challenging to write correctly. In the [vision document][vision-blog] process we underwent last year, we received a number of reports on how this was [extremely difficult][alan-stream] and a [source of bugs][barbara-mutex] for Rust users.
109125

110126
In fact, the difficulty of writing manual poll implementations was a primary reason for adding async/await to the core language in the first place.
111127

112-
## What's new
128+
## What's supported in nightly
113129

114130
We've been working to support `async fn` directly in traits, and an implementation [recently landed][initial-impl] in nightly! The feature still has some rough edges, but let's take a look at what you can do with it.
115131

@@ -122,7 +138,7 @@ trait Database {
122138
async fn fetch_data(&self) -> String;
123139
}
124140

125-
impl Database for MyDatabase {
141+
impl Database for MyDb {
126142
async fn fetch_data(&self) -> String { ... }
127143
}
128144
```
@@ -138,7 +154,7 @@ trait AsyncIterator {
138154
}
139155
```
140156

141-
There's a decent chance that exactly this trait will end up in the standard library. For now though, you can use the one in the [`async_iterator` crate](https://docs.rs/async-iterator/latest/async_iterator/) and use it in generic code, just like you would normally.
157+
There's a decent chance that exactly this trait will end up in the standard library! For now though, you can use the one in the [`async_iterator` crate](https://docs.rs/async-iterator/latest/async_iterator/) and write generic code with it, just like you would normally.
142158

143159
```rust
144160
async fn print_all<I: AsyncIterator>(mut count: I)
@@ -153,7 +169,7 @@ where
153169

154170
### Limitation: Spawning from generics
155171

156-
However, there is a catch! If you try to spawn from a generic function like `print_all`, and (like most async users), you use a work stealing executor that requires spawned tasks to be `Send`, you'll hit an error which is not easily resolved.[^actual-error]
172+
There is a catch! If you try to *spawn* from a generic function like `print_all`, and (like the majority of async users) you use a work stealing executor that requires spawned tasks to be `Send`, you'll hit an error which is not easily resolved.[^actual-error]
157173

158174
```rust
159175
fn spawn_print_all<I: AsyncIterator + Send + 'static>(mut count: I)
@@ -174,7 +190,7 @@ where
174190

175191
[^actual-error]: The actual error message produced by the compiler is a bit noisier than this, but that can be improved.
176192

177-
Even though we added a `Send` bound on `I` in this function, it's not enough. We need to say that the *future* returned by `next()` is `Send`, but we can't: async functions return anonymous types, and you can't bound anonymous types in Rust.
193+
You can see that we added an `I: Send` bound in the function signature, but that was not enough. To satisfy this error we need to say that the *future returned by `next()`* is `Send`. But as we saw at the beginning of this post, async functions return anonymous types. There's no way to express bounds on those types.
178194

179195
There are potential solutions to this problem that we'll be exploring in a follow-up post. But for now, there are a couple things you can do to get out of a situation like this.
180196

@@ -188,9 +204,10 @@ async fn do_something() {
188204
executor::spawn(print_all(iter));
189205
}
190206
```
191-
[play-concrete-spawn]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=fc6b1ef2060ac8a79079ad8d59822727
192207

193-
This works without any `Send` bounds whatsoever! This works because the function we're spawning from knows the concrete type of our iterator, `Countdown`. The compiler knows that that type is `Send`, and that it returns a future that is `Send`.[^auto-traits-special]
208+
[play-concrete-spawn]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=6ffde69ba43c6c5094b7fbdae11774a9
209+
210+
This works without any `Send` bounds whatsoever! This works because `do_something` knows the concrete type of our iterator, `Countdown`. The compiler knows that this type is `Send`, and that `print_all(iter)` therefore produces a future that is `Send`.[^auto-traits-special]
194211

195212
One hypothesis is that while people will hit this problem, they will encounter it relatively infrequently, because most of the time `spawn` won't be called in code that's generic over a trait with async functions.
196213

@@ -200,7 +217,7 @@ We would like to start gathering data on people's actual experiences with this.
200217

201218
Eventually you probably *will* want to spawn from a context that's generic over an async trait that you call. What then!?
202219

203-
For now it's possible to use another new nightly-only feature, `return_position_impl_trait_in_trait`, to express this directly in your trait:
220+
For now it's possible to use another new nightly-only feature, `return_position_impl_trait_in_trait`, to express the bound you need directly in your trait:
204221

205222
```rust
206223
#![feature(return_position_impl_trait_in_trait)]
@@ -219,7 +236,7 @@ This solution is intended to be temporary. We'd like to implement a better solut
219236

220237
### Limitation: Dynamic dispatch
221238

222-
There's one final limitation: You can't call an `async fn` with a `dyn Trait`. Designs to support this exist[^dyn-designs], but are in the earlier stages. If you need dynamic dispatch from a trait, you're much better off using the `async_trait` crate.
239+
There's one final limitation: You can't call an `async fn` with a `dyn Trait`. Designs to support this exist[^dyn-designs], but are in the earlier stages. If you need dynamic dispatch from a trait, you're better off using the `async_trait` macro for now.
223240

224241
## Path to stabilization
225242

@@ -230,7 +247,7 @@ The async working group would like to get something useful in the hands of Rust
230247
There are two big questions to answer first:
231248

232249
* **Do we need to solve the "spawning from generics" (`Send` bound) problem first?** Please leave feedback on [this issue][send-bound-issue].
233-
* **What other bugs and quality issues exist?** Please file [new issues](https://github.com/rust-lang/rust/issues/new/choose) for these. Also see [known issues](https://github.com/rust-lang/rust/labels/F-async_fn_in_trait).
250+
* **What other bugs and quality issues exist?** Please file [new issues](https://github.com/rust-lang/rust/issues/new/choose) for these. You can view [known issues here](https://github.com/rust-lang/rust/labels/F-async_fn_in_trait).
234251

235252
If you're an async Rust enthusiast and are willing to try experimental new features, we'd very much appreciate it if you gave it a spin!
236253

@@ -249,15 +266,15 @@ This work was made possible thanks to the efforts of many people, including
249266
* Niko Matsakis
250267
* Tyler Mandry
251268

252-
In addition it was built on top of years of compiler work that enabled us to ship [GATs] as well as other fundamental type system improvements. Thanks to all those who contributed.
269+
In addition it was built on top of years of compiler work that enabled us to ship [GATs] as well as other fundamental type system improvements. I'm deeply grateful to all those who contributed; this work would not be possible without you. Thank you!
253270

254271
To learn more about this feature and the challenges behind it, check out the [Static async fn in traits RFC][RFC] and [why async fn in traits are hard]. Also stay tuned for a follow-up post where we explore language extensions that make it possible to express `Send` bounds without a special trait.
255272

256273

257-
_Thanks to Yoshua Wuyts, Dan Johnson, Santiago Pastorino, and Eric Holk for reviewing a draft of this post._
274+
_Thanks to Yoshua Wuyts, Nick Cameron, Dan Johnson, Santiago Pastorino, Eric Holk, and Niko Matsakis for reviewing a draft of this post._
258275

259276

260-
[^auto-traits-special]: But how does the compiler know that the future returned by `print_all`, the one we pass to `spawn`, is `Send`? Auto traits like `Send` and `Sync` are special in this way. The compiler knows that the return type of `print_all` is `Send` if and only if the conditions above are satisfied, and unlike with normal traits, it is allowed to use this knowledge when type checking your program.
277+
[^auto-traits-special]: Auto traits such as `Send` and `Sync` are special in this way. The compiler knows that the return type of `print_all` is `Send` if and only if the type of its argument `Send`, and unlike with regular traits, it is allowed to use this knowledge when type checking your program.
261278
[^dyn-designs]: See [Async fn in dyn trait](https://rust-lang.github.io/async-fundamentals-initiative/explainer/async_fn_in_dyn_trait.html) on the initiative website, as well as posts 8 and 9 in [this series](https://smallcultfollowing.com/babysteps/blog/2022/09/21/dyn-async-traits-part-9-callee-site-selection/).
262279

263280
[initial-impl]: https://github.com/rust-lang/rust/pull/101224

0 commit comments

Comments
 (0)