Skip to content

Commit 46a4526

Browse files
authored
Respond to feedback, add playground links
1 parent d4eebe0 commit 46a4526

File tree

1 file changed

+28
-8
lines changed

1 file changed

+28
-8
lines changed

posts/inside-rust/2023-05-02-stabilizing-async-fn-in-trait.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,22 @@ async fn do_health_check(hc: impl HealthCheck) {
4343
}
4444
```
4545

46-
**Status:** This functionality was described in [RFC 3185], merged on Dec 7, 2021, and was covered in detail in our [previous blog post][pp].
46+
**Status:** This functionality was described in [RFC 3185], merged on Dec 7, 2021, and is available in nightly. It was covered in detail in our [previous blog post][pp].
47+
48+
[Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2399715f67d8eb0064efd6c8e47532f7)
4749

4850
[RFC 3185]: https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html
4951

5052

5153
## MVP Part 2: Send bounds and associated return types
5254

53-
There is one complication that arises when using async functions in traits that doesn't arise with sync functions. Many async runtimes -- notably including the default configurations of [Tokio] and [async-std] -- use a workstealing thread scheduler. This means that futures may move between worker threads dynamically to achieve load balancing. As a result, the future must only capture `Send` data.
55+
There is one complication that arises when using async functions in traits that doesn't arise with sync functions. Many async runtimes -- notably including the default configurations of [Tokio] and [async-std] -- use a work stealing thread scheduler. This means that futures may move between worker threads dynamically to achieve load balancing. As a result, the future must only capture `Send` data.
5456

5557
[Tokio]: https://tokio.rs/
5658

5759
[async-std]: https://async.rs/
5860

59-
If you author a generic async function that spawns tasks on one of those runtimes, however, you will start to get compilation errors ([playground](XXX)):
61+
If you author a generic async function that spawns tasks on one of those runtimes, however, you will start to get compilation errors ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c033cf6174ff7d44e5673ecd254e6e69)):
6062

6163
```rust
6264
async fn do_health_check_par(hc: impl HealthCheck) {
@@ -101,9 +103,11 @@ In our [previous post][pp], we [hypothesized](https://blog.rust-lang.org/inside-
101103

102104
**Status:** Associated return types have an experimental implementation and we are currently drafting an RFC. There are several open bugs that will need to be fixed.
103105

106+
[Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2066934a05cb9eafc0b47af7bdf8c57f)
107+
104108
## MVP Part 3: "impl trait in traits" (return position)
105109

106-
All across Rust, an async function is "syntactic sugar" for a function that returns an `impl Future` -- and async functions in traits are no exception. As part of the MVP, we plan to stabilize the use of `-> impl Trait` notation in traits.
110+
In Rust an async function is "syntactic sugar" for a function that returns an `impl Future`, and async functions in traits are no exception. As part of the MVP, we plan to stabilize the use of `-> impl Trait` notation in traits and trait impls.
107111

108112
Impl trait in traits has all kinds of uses, but one common one for async programming is to avoid capturing all of the function arguments by doing some amount of sync work and then returning a future for the rest. For example, this `LaunchService` trait declares a `launch` function that does not capture `self` (similar to the existing Tower [`Service`] trait):
109113

@@ -121,15 +125,29 @@ trait LaunchService {
121125
}
122126
```
123127

128+
Since `async fn` is sugar for a regular function returning `impl Future`, these two syntactic forms will work interchangeably.
129+
130+
```rust
131+
trait HealthCheck {
132+
async fn check(&mut self) -> bool;
133+
}
134+
135+
impl HealthCheck for MyType {
136+
fn check(&mut self) -> impl Future<Output = bool> + '_ { ... }
137+
}
138+
```
139+
124140
Even though the need for "impl trait in traits" comes up a lot in async, they are a general feature that will be useful in many contexts having nothing to do with async (for example, returning iterators from trait methods).
125141

126142
**Status:** Return-position impl trait in traits have an experimental implementation and are described in the recently opened [RFC 3425].
127143

144+
[Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=75cfc199cc50a111576c2d8e342ae823)
145+
128146
[RFC 3425]: https://github.com/rust-lang/rfcs/pull/3425
129147

130148
## Evaluating the MVP
131149

132-
To evaluate the utility of this MVP, the working group collected [five case studies][] covering the [builder-provider pattern used in the AWS SDK](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/builder-provider-api.html#dynamic-dispatch-behind-the-api); the potential use of async function in traits in [tower][cst] and the actual use in [embassy][cse], the [Fuchsia networking stack][] and [an internal Microsoft tool][]. These studies validated that the above functionality is sufficient to use async function in traits for all kinds of things, though some situations require workarounds (hence the "MVP" title).
150+
To evaluate the utility of this MVP, the working group collected [five case studies] covering the [builder-provider pattern used in the AWS SDK](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/builder-provider-api.html#dynamic-dispatch-behind-the-api); the potential use of async function in traits in [tower][cst] and the actual use in [embassy][cse], the [Fuchsia networking stack] and [an internal Microsoft tool]. These studies validated that the above functionality is sufficient to use async function in traits for all kinds of things, though some situations require workarounds (hence the "MVP" title).
133151

134152
[Fuchsia networking stack]: https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/socket-handler.html
135153

@@ -147,9 +165,11 @@ The case studies revealed two situations that the MVP doesn't support very well,
147165

148166
### Modeling dynamic dispatch
149167

150-
In the MVP, traits that use async functions are not "dyn safe", meaning that they don't support dynamic dispatch. So e.g. given the `HealthCheck` trait we saw earlier, one could not write `Box<dyn HealthCheck>`. At first, this seems like a crucial limitation, since many of the use cases require dynamic dispatch! But it turns out that there is a workaround. One can define an "erased" trait internally to your crate that enables dynamic dispatch. The process was pioneered by crates like [erased serde][] and is explained in detail in the [builder-provider case study][].
168+
In the MVP, traits that use async functions are not "dyn safe", meaning that they don't support dynamic dispatch. So e.g. given the `HealthCheck` trait we saw earlier, one could not write `Box<dyn HealthCheck>`.
169+
170+
At first, this seems like a crucial limitation, since many of the use cases require dynamic dispatch! But it turns out that there is a workaround. One can define an "erased" trait internally to your crate that enables dynamic dispatch. The process was pioneered by crates like [erased serde] and is explained in detail in the [builder-provider case study].
151171

152-
In the future, async fn should work with `dyn Trait` directly.
172+
To make this workaround easier in the near term, we hope to provide a proc macro to automate it. In the future, async fn should work with `dyn Trait` directly.
153173

154174
[erased serde]: https://github.com/dtolnay/erased-serde
155175
[builder-provider case study]: https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/builder-provider-api.html#dynamic-dispatch-behind-the-api
@@ -172,7 +192,7 @@ where
172192
{}
173193
```
174194

175-
Using a pattern like this means you can write `T: SendHealthCheck`. In the future, something like [trait transformers][] may provide a more concise syntax.
195+
Using a pattern like this means you can write `T: SendHealthCheck`. We hope to provide a proc macro to write these trait aliases for you. In the future, something like [trait transformers] may provide a more concise syntax.
176196

177197
[trait transformers]: https://smallcultfollowing.com/babysteps/blog/2023/03/03/trait-transformers-send-bounds-part-3/
178198

0 commit comments

Comments
 (0)