Skip to content

Commit 6b0277d

Browse files
committed
fixup! status-quo story: Alan needs async in traits
1 parent 196641e commit 6b0277d

File tree

1 file changed

+17
-15
lines changed

1 file changed

+17
-15
lines changed

src/vision/status_quo/alan_needs_async_in_traits.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee
1010

1111
Alan is working on a project with Barbara which has already gotten off to a [somewhat rocky start](./barbara_anguishes_over_http.md). He is working on abstracting away the HTTP implementation the library uses so that users can provide their own. He wants the user to implement an async trait called `HttpClient` which has one method `perform(request: Request) -> Response`. Alan tries to create the async trait:
1212

13-
```rust,ignore
13+
```rust
1414
trait HttpClient {
1515
async fn perform(request: Request) -> Response;
1616
}
1717
```
1818

1919
When Alan tries to compile this, he gets an error:
2020

21-
```ignore
21+
```
2222
--> src/lib.rs:2:5
2323
|
2424
2 | async fn perform(request: Request) -> Response;
@@ -31,27 +31,30 @@ When Alan tries to compile this, he gets an error:
3131
```
3232

3333
Alan, who has been using Rust for a little while now, has learned to follow compiler error messages and adds `async-trait` to his `Cargo.toml`. Alan follows the README of `async-trait` and comes up with the following code:
34-
'''rust
34+
35+
```rust
3536
#[async_trait]
3637
trait HttpClient {
3738
async fn perform(request: Request) -> Response;
3839
}
39-
'''
40+
```
41+
4042
Alan's code now compiles, but he also finds that his compile times have gone from under a second to around 6s, at least for a clean build.
4143

42-
**TODO::** Maybe add representive pre and post trait compile times here.
44+
After Alan finishes adding the new trait, he shows his work off to Barbara and mentions he's happy with the work but is a little sad that compile times have worsened. Barbara, an experienced Rust developer, knows that using `async-trait` comes with some additional issues. In this particular case she is especially worried about tying their public API to a third-party dependency. Even though it is technically possible to implement traits annotated with `async_trait` without using `async_trait`, doing so in practice is very painful. For example `async_trait`:
45+
46+
* handles lifetimes for you if the returned future is tied to the lifetime of some inputs.
47+
* boxes and pins the futures for you.
4348

44-
After Alan finishes adding the new trait, he shows his work off to Barbara and mentions he's happy with the work but is a little sad that compile times have worsened. Barbara, an experienced Rust developer, knows that using `async-trait` comes with some issues and in this case is especially worried about tying their public API to a third-party dependency. She decides to not worry Alan with this right now. Alan and Barbara are pretty happy with the results and go on to publish their crate which gets lots of users.
49+
which implementer will have to manually handle if they don't use `async_trait`. She decides to not worry Alan with this right now. Alan and Barbara are pretty happy with the results and go on to publish their crate which gets lots of users.
4550

4651
Later on, a potential user of the library wants to use their library in a `no_std` context where they will be providing a custom HTTP stack. Alan and Barbara have done a pretty good job of limiting the use of standard library features and think it might be possible to support this use case. However, they quickly run into a show stopper: `async-trait` boxes all of the futures returned from a async trait function. They report this to Alan through an issue.
4752

4853
Alan, feeling (over-) confident in his Rust skills, decides to try to see if he can implement async traits without using `async-trait`.
4954

5055
```rust
51-
struct Request(String);
52-
5356
trait HttpClient {
54-
type Response: Future<Output = ()>;
57+
type Response: Future<Output = Response>;
5558

5659
fn perform(request: Request) -> Self::Response;
5760
}
@@ -61,15 +64,15 @@ Alan seems to have something working, but when he goes to update the examples of
6164

6265
* use trait object:
6366

64-
```rust,ignore
67+
```rust
6568
struct ClientImpl;
6669

6770
impl HttpClient for ClientImpl {
68-
type Response = Pin<Box<dyn Future<Output = ()>>>;
71+
type Response = Pin<Box<dyn Future<Output = Response>>>;
6972

7073
fn perform(request: Request) -> Self::Response {
7174
Box::pin(async move {
72-
println!("{}", request.0);
75+
// Some async work here creating Reponse
7376
})
7477
}
7578
}
@@ -79,7 +82,7 @@ Alan seems to have something working, but when he goes to update the examples of
7982

8083
* implement `Future` trait manually, which isn't particulary easy/straight-forward for non-trivial cases, especially if it involves making other async calls (likely).
8184

82-
**TODO::** Would make sense for Alan to reach out to Barbara at this point and she should be able to give some good advice.
85+
After a lot of thinking and discussion, Alan and Barbara accept that they won't be able to support `no_std` users of their library and add mention of this in crate documentation.
8386

8487
## 🤔 Frequently Asked Questions
8588

@@ -88,7 +91,6 @@ Alan seems to have something working, but when he goes to update the examples of
8891
* `async-trait` is awesome, but has some drawbacks
8992
* compile time increases
9093
* performance cost of boxing and dynamic dispatch
91-
* lack of control over whether futures are `Send` or `Sync`
9294
* not a standard solution so when this comes to language, it might break things
9395
* Trying to have a more efficient implementation than `async-trait` is likely not possible.
9496

@@ -97,7 +99,7 @@ Alan seems to have something working, but when he goes to update the examples of
9799
* [Zeeshan](https://github.com/zeenix/) is looking for a way to implement async version of the [service-side zbus API](https://docs.rs/zbus/1.9.1/zbus/trait.Interface.html).
98100
* [Ryan](https://github.com/rylev) had to use `async-trait` in an internal project.
99101

100-
## **Why did you choose Alan to tell this story?**
102+
### **Why did you choose Alan to tell this story?**
101103

102104
We could have used Barbara here but she'd probably know some of the work-arounds (likely even the details on why they're needed) and wouldn't need help so it wouldn't make for a good story. Having said that, Barbara is involved in the story still so it's not a pure Alan story.
103105

0 commit comments

Comments
 (0)