Skip to content

Commit 9253739

Browse files
authored
Merge pull request #134 from zeenix/alan_needs_async_in_traits
status-quo story: Alan needs async in traits
2 parents 6f70b71 + de6660f commit 9253739

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- [Alan started trusting the Rust compiler, but then... async](./vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md)
3333
- [Alan tries to debug a hang](./vision/status_quo/alan_tries_to_debug_a_hang.md)
3434
- [Alan writes a web framework](./vision/status_quo/alan_writes_a_web_framework.md)
35+
- [Alan needs async in traits](./vision/status_quo/alan_needs_async_in_traits.md)
3536
- [Barbara anguishes over HTTP](./vision/status_quo/barbara_anguishes_over_http.md)
3637
- [Barbara carefully dismisses embedded `Future`](./vision/status_quo/barbara_carefully_dismisses_embedded_future.md)
3738
- [Barbara makes their first foray into async](./vision/status_quo/barbara_makes_their_first_steps_into_async.md)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# 😱 Status quo stories: Alan needs async in traits
2+
3+
## 🚧 Warning: Draft status 🚧
4+
5+
This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today.
6+
7+
If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]!
8+
9+
## The story
10+
11+
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:
12+
13+
```rust
14+
trait HttpClient {
15+
async fn perform(request: Request) -> Response;
16+
}
17+
```
18+
19+
When Alan tries to compile this, he gets an error:
20+
21+
```
22+
--> src/lib.rs:2:5
23+
|
24+
2 | async fn perform(request: Request) -> Response;
25+
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
| |
27+
| `async` because of this
28+
|
29+
= note: `async` trait functions are not currently supported
30+
= note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
31+
```
32+
33+
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+
35+
```rust
36+
#[async_trait]
37+
trait HttpClient {
38+
async fn perform(request: Request) -> Response;
39+
}
40+
```
41+
42+
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.
43+
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.
48+
49+
which the 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.
50+
51+
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.
52+
53+
Alan, feeling (over-) confident in his Rust skills, decides to try to see if he can implement async traits without using `async-trait`.
54+
55+
```rust
56+
trait HttpClient {
57+
type Response: Future<Output = Response>;
58+
59+
fn perform(request: Request) -> Self::Response;
60+
}
61+
```
62+
63+
Alan seems to have something working, but when he goes to update the examples of how to implement this trait in his crate's documentation, he realizes that he either needs to:
64+
65+
* use trait object:
66+
67+
```rust
68+
struct ClientImpl;
69+
70+
impl HttpClient for ClientImpl {
71+
type Response = Pin<Box<dyn Future<Output = Response>>>;
72+
73+
fn perform(request: Request) -> Self::Response {
74+
Box::pin(async move {
75+
// Some async work here creating Reponse
76+
})
77+
}
78+
}
79+
```
80+
81+
which wouldn't work for `no_std`.
82+
83+
* implement `Future` trait manually, which isn't particulary easy/straight-forward for non-trivial cases, especially if it involves making other async calls (likely).
84+
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.
86+
87+
## 🤔 Frequently Asked Questions
88+
89+
### **What are the morals of the story?**
90+
91+
* `async-trait` is awesome, but has some drawbacks
92+
* compile time increases
93+
* performance cost of boxing and dynamic dispatch
94+
* not a standard solution so when this comes to language, it might break things
95+
* Trying to have a more efficient implementation than `async-trait` is likely not possible.
96+
97+
### **What are the sources for this story?**
98+
99+
* [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).
100+
* [Ryan](https://github.com/rylev) had to use `async-trait` in an internal project.
101+
102+
### **Why did you choose Alan to tell this story?**
103+
104+
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.
105+
106+
### **How would this story have played out differently for the other characters?**
107+
108+
* Barbara: See above.
109+
* Grace: Probably won't know the solution to these issues much like Alan, but might have an easier time understanding the **why** of the whole situation.
110+
* Niklaus: would be lost - traits are somewhat new themselves. This is just more complexity, and Niklaus might not even know where to go for help (outside of compiler errors).

src/vision/status_quo/alan_tries_to_debug_a_hang.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ Disgruntled, Alan begins the arduous, boring task of instrumenting the applicati
130130
* Some of the characters with either a background in Rust or a background in systems programming might know that Rust's async doesn't always map to an underlying system feature and so they might expect that `gdb` or `lldb` is unable to help them.
131131
* Barbara, the experienced Rust dev, might also have used a tracing/instrumentation library from the beginning and have that to fall back on rather than having to do the work to add it now.
132132

133-
[character]: ../characters.md
133+
[characters]: ../characters.md
134134
[status quo stories]: ./status_quo.md
135135
[Alan]: ../characters/alan.md
136136
[Grace]: ../characters/grace.md

0 commit comments

Comments
 (0)