---
title: "Design meeting 2024-05-16: Project goals for WG-async"
tags: ["WG-async", "design-meeting", "minutes"]
date: 2024-05-16
discussion: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/Design.20meeting.202024-05-16
url: https://hackmd.io/ZAMzuBhmQ-29LRPC7rD1qw
---
# Async Rust
*This is extracted from some "WIP" files on Niko's computer. Fodder for discussion.*
## Async fn everywhere
Async Rust is a crucial growth area, with a full 52% of the respondents in the [2023 Rust survey](https://blog.rust-lang.org/2024/02/19/2023-Rust-Annual-Survey-2023-results.html) indicating that they use Rust to build server-side or backend applications. Despite that success, async is *also* the most frequently reported challenge to learning Rust.
Our goals for 2024H2 are to stabilize async closures; solve the send bound problem; and stabilize a trait for async iteration (also called streams). These goals will help propel us towards our long-term vision that async Rust should feel the same as sync Rust, with access to the same range of language support (traits, closures, dynamic dispatch, destructors), library support (including a clear "getting started" experience), and documentation (coverage in the Rust book).
| Goal | Status | Owner | Teams |
| ----------------------------------------------- | ----------- | ------------------- | ------------------ |
| [Revise async vision doc][] | ![WIP][wip] | [tmandry][] | [Lang], [Libs-API] |
| [Stablilize async closures][] | ![WIP][wip] | [compiler-errors][] | [Lang], [Libs-API] |
| [Stable soluton for the "Send bound problem"][] | ![WIP][wip] | [nikomatsakis][] | [Lang], [Libs-API] |
| [Stable trait for async iteration][] | ![WIP][wip] | [eholk][] | [Lang], [Libs-API] |
[Revise async vision doc]: ./Async.md
[Stablilize async closures]: ./Async--AsyncClosures.md
[Stable soluton for the "Send bound problem"]: Async--SendBounds.md
[Stable trait for async iteration]: ./Async--Streams.md
## Revise async vision doc
This is kind of a "meta" goal, I am fishing for the right shape, but it comes down to laying out our general philosophy around async.
### The status quo
Despite the growth of async Rust, it continues to be significantly more difficult to use. As one engineer from Amazon put it, Async Rust is "Rust on hard mode". Some of the key challenges to address are:
- Getting started:
- **Good learning material is out there, but hard to find.** The lack of "standard" recommendations makes it [harder to direct people who are just getting started](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.html).
- **Fragmentation:** Every Rust async program must pick a runtime. Libraries that make use of non-trivial functionality must be written for one runtime. Combining runtimes sometimes works and sometimes doesn't, leading to [surprise failures when you try to run your program](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.html).
- Getting your program to do what you want:
- **Cancellation, `select!`, and other primitives considered harmful:** Many widely used APIs have sharp edges, such as [buffering issues](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html), surprise cancellation, [difficult resource cleanup](https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/alan_finds_database_drops_hard.html), etc.
- **Cannot use references from inside tasks:** Spawning tasks are the solution to many of the above problems, but tasks cannot share references.
- **Poll model scales poorly sometimes:** Complex futures like `FuturesUnordered` or joining a large number of tasks can have very poor performance because of the limits of the poll API.
- Getting your program to run as fast as you want -- mostly works, but some challenges:
- **Optimizing poll times is hard:**
- **Future sizes are too big:**
### Design axioms
* **We lay the foundations for a thriving ecosystem.**
* **Uphold sync's Rust bar for reliability.**
* **Zero-cost.**
* **From embedded to the cloud.**
* **Consistent, incremental progress.**
## Stabilizing async closures
*Some notes from this goal text.*
### The status quo
Async combinator-like APIs today typically make use an ordinary Rust closure that returns a future,
such as the `filter` API from [`StreamExt`](https://docs.rs/futures/latest/futures/prelude/stream/trait.StreamExt.html#method.filter):
```rust
fn filter<Fut, F>(self, f: F) -> Filter<Self, Fut, F>
where
F: FnMut(&Self::Item) -> Fut,
Fut: Future<Output = bool>,
Self: Sized,
```
This approach however does not allow the closure to access variables captured by reference from its environment:
```rust
let mut accept_list = vec!["foo", "bar"]
stream
.filter(|s| async { accept_list.contains(s) })
```
The reason is that data captured from the environment is stored in `self`.
But the signature for sync closures does not permit the return value (`Self::Output`) to borrow from `self`:
```rust
trait FnMut<A>: FnOnce<A> {
fn call_mut(&mut self, args: A) -> Self::Output;
}
```
To support natural async closures, a trait is needed where `call_mut` is an `async fn`. Or, desugared, something that is equivalent to:
```rust
trait AsyncFnMut<A>: AsyncFnOnce<A> {
fn call_mut<'s>(&'s mut self, args: A) -> use<'s, A> impl Future<Output = Self::Output>;
// ^^^^^^^^^^ note that this captures `'s`
}
```
### The next few steps
The goal for this year to be able to
* support some "async equivalent" to `Fn`, `FnMut`, and `FnOnce` bounds
* this should be usable in all the usual places
* support some way to author async closure expressions
These features should be sufficient to support methods like `filter` above.
The details (syntax, precise semantics) will be determined via experimentation and subject to RFC.
### The "shiny future" we are working towards
This goal is part of a path to extend Rust's async support for all the places one might write `fn`:
* async fn in inherent methods (done in 2019)
* async fn in traits, static dispatch (done in 2023)
*
* async fn in traits, dynamic dispatch
See the [async abstractions](./Async--Abstractions.md) goal.
## Stable soluton for the "Send bound problem"
*TL;DR*: RTN. RFC to be opened soon.
## Stable trait for async iteration
*TL;DR*: Author an RFC and stabilize a trait for async iteration.
---
# Discussion
## Attendance
- People: Eholk, Niko, TC, Yosh, Daniel Lu, Tmandry, Adam Leventhal, David Barsky, Vincenzo Palazzo
## Meeting roles
- Minutes, driver: TC
## Async Destructors
yosh: We've seen meaningful progress on async destructors, driven by the Huawei folks. This seems to be one of the main language features missing from the language right now - I'm surprised this isn't a priority on the list given the importance of it?
yosh: To name a practical example: We place value on structured concurrency - but we can't meaningfully guarantee it without async destructors.
nikomatsakis: Good callout! I meant to have "complete X of the Y async drop experiments that were outlined in MCP #XXX".
TC: There's also the question of whether we'd have an owner here, given the funding situation.
NM: Indeed; part of the goal of project goals is to help people get that funding.
## Overarching plan
nikomatsakis: I put in "revise async vision doc" as a kind of placeholder here. I'm not sure the right shape, this might be it, but the overall goal is to say "let's take these logical steps but let's also get more aligned on our longer term plan". A revision of the vision doc feels like one option. A prior version of this was like "let's do some prototyping and exploration and author an evaluation". Basically I want to put us in a good position for heading into next year. Another part of this might be revisiting the async WG *structure* -- I personally don't feel a "WG" (informal entity with no explicit 'power') is really meeting our needs, though I'm not yet decided on what I think ought to replace it.
tmandry: There seem to be many subgoals here. I'd like to be clear on what it means to be "done" with this goal. This does all seem related. We never really finished the original async vision doc, so this could be a good chance to do that.
NM: It would be interesting to talk about what it means for it to be "done". Should it be FCPed by lang and libs, e.g.?
tmandry: One thing that feels intimidating about the async vision is that it is the "async vision", and that could include many things. So I'd probably like to pare back our ambitions here to what seems obvious in the moment.
NM: That's a good point. I'd point to the design axioms point here; that's meant to go in that direction. Maybe that helps here. It's not every part of the async experience; it's the basic parts of how you interact with async code.
Adam: Might it help to put a timeline on this? Then we're only speaking of what is the goal within a particular time scope.
tmandry: That helps in some ways and hurts in others. I had originally, e.g. tried writing an "async in 2030" doc. That would allow getting all the things in, e.g.
NM: What are your thoughts on how to scope it?
tmandry: The scope is the scope of all the things floating around in all of our collective heads at the moment. That's maybe not that helpful. But, e.g., we don't need to say much about async closures other than that we want them.
tmandry: Starting from a place of "here's your experience when you write your first async program", e.g. with respect to the overall structure, and casting a vision for how interop looks, may be helpful.
Yosh: What do we have in mind as the purpose of defining that vision? Who are we hoping to target with that?
NM: I want there to be something we can refer back to with respect, e.g., to the experience that we want to create. It won't have every detail.
eholk: In terms of whom this is for, for me it was good to see an overview of the problems, e.g. the status quo stories. It helped with seeing where we need to expend effort. It helped perhaps even at an emotional level to understand where people might struggle.
tmandry: There are two goals I see. As Niko mentioned, control flow is one thing I was thinking -- task spawning, joining, merging, concurrency. The other one paints a broader picture of what is our goal. It provides useful signaling both to us and to the rest of the ecosystem. What do we see as the actual shape of the solution for this problem? This is what I had in mind for interop, e.g. Are we going to accept that there's just one executor? Or do we want an ecosystem of libraries that can be generic over it?
NM: There are probably middle grounds there too. I don't know the write answer, but I could imagine that your program still says what executor you're using, but that the libraries you pull in could be generic over these.
Yosh: That probably ties into the next question.
NM: We can move to that question. Another way this might look is to have a sketch of how the standard library might look.
## maybe-async and a plan for asyncifying the stdlib
yosh: Earlier in the year Niko put forward the ask, I'm not sure where again, to drive a decision on effect generics. I've been writing draft RFCs with the intent to actually be able to make a decision on it.
yosh: It feels like that should fall on here somewhere? Not that effect generics or maybe-async should be a goal onto themselves, but I want us to come to a decision of how we plan to evolve the stdlib to gain async functionality. Right now we haven't formulated a concrete plan or vision for this, and this seems important as it will determine both the organization and scope of APIs.
NM: This is the post that Yosh is mentioning:
https://smallcultfollowing.com/babysteps/blog/2024/01/03/async-rust-2024/
Yosh: I want us to be making conscious decisions here. We shouldn't sleepwalk into things.
tmandry: I want to challenge that a bit. We should set out a vision based on the things we're confident in. Then that helps to better understand more things, and that helps with later iterations of defining new visions.
tmandry: In terms of defining async interfaces in the standard library, I don't have strong opinions on that, and I feel like we could make progress on other things and build to there, iteratively.
Yosh: I'm more interested in us answering the questions than in any specific answer. E.g., I see certain problems that lead to my work on effect generics, but that's maybe not the only solution. I want us to explore the interactions here. I'm seeing a shape of asyncifying things as a broader trend. If we take it one API at a time, I feel like we'll end up with something incoherent. So I'd like for us to come up with a coherent strategy. I feel like we did that with `const`, e.g. Similar to that, there would be gradual asyncification, but with a coherent plan.
tmandry: I see it as a process of managing uncertainty and knowing where the connections are. So it'd be a failure if we didn't realize there were connections somewhere. That's perhaps where prototyping comes in.
David: Generally I wanted to say the same thing as tmandry in terms of iterative progress. E.g., my recent activity to aggressively agitate for the stabilization of `AsyncIterator` (to "stir shit up") is driven by the sense that stabilizing it now is more valuable than the cost of any incoherence we may later hit.
NM: My feeling is that the questions that Yosh raises are the questions that the vision document is intended to answer, with the caveat that perhaps all of the questions, e.g. with AsyncIO, is probably further ahead than we really need to look.
NM: The way I saw it when I wrote the goals the way I did is in the spirit of iterative progress. One of the guiding axioms I set out, e.g., is the zero cost element. A key part of our target market is the high performance. That's our unique advantage that would turn people toward Rust instead of other options. I see the vision docs as seeing further ahead while being guided by the realities of what we need to do in the shorter term. That's what I'd like to see.
Yosh: There all all of these little questions that flow through the standard library. E.g., do we need an async version of the `Debug` trait. `Option::map`. Because otherwise they'll stay as paper cuts.
TC: I agree with what others have said here. I'd highlight in particular what tmandry said about developing a vision iteratively. There's a fog of war on these design decisions; there's only so far ahead that we can look with any confidence, and moving forward helps us to see more clearly.
TC: At the same time, I'm sympathetic to the points that Yosh raises. We do want to end up with a coherent result.
TC: My view is that the lang team owns the *flavor* of the language, inclusive of the standard library. The shape of the standard library is mostly dictated by the features that are available in the language, and if we ever noticed the smart people on libs-api creating results we didn't like, that would make us think hard about what we need to do in the language to fix that. I think of WG-async in the similar way, as a lang-like team focused on the async parts of the language, and with a similar mandate that bleeds over into what that means for the flavor of the standard library with respect to async. The coherence that Yosh mentions is part of this.
## Documentation
eholk: Nice to see the bullet point about documentation. I was talking with nrc a little while ago and he's interested in rewriting the async book, if he can get funding for it. So if anyone has spare piles of cash lying around, that might be a good place to send it.
nikomatsakis: Oh, I should have said, part of this is that I would like to have "unfunded" goals -- things we think we'd like to see, and we think we have the bandwidth/ability to support, but for which we lack a compelling owner. Revise the async book might be one of those.
Adam: is this the book? https://rust-lang.github.io/async-book/ -- is that considered to be finished?
eholk: That's the book. I don't think it's finished.
Adam: If there's not a clear articulation of when async is appropriate or not, then that undermines some of the work being done.
NM: You're saying, e.g., that the book would be a higher priority than async closures?
Adam: Yes, but of course, I'm a bit out of the loop on the new features being discussed. Being able to articulate how users should interact with what already exists would be helpful. But also, writing documentation often helps to produce a better artifact, because it forces you to explain it to users.
tmandry: This is mostly what I have in mind for the async vision doc, except that I probably have in mind a different audience.
Adam: These are probably complementary. Starting with the user documentation may be the best way in terms of helping people use the primitives in the way they're meant to be used.
NM: We should align not just on the shape of the solution but also articulate on the problems that we see. The original versions of the stories helped us do that, but perhaps we didn't lift that up enough.
NM: One of the weird things about open source is that prioritization only kind of matters. Things happen when somebody comes in when people come in to drive them. So that's sometimes a tragedy, but it's how things work. For me, e.g., landing async closures is a much higher priority. So I'm sympathetic to Adam's point, but not enough to redirect the people under my control to work on it.
eholk: This reminds me of the "user guide from the future". Maybe that's a way to combine this.
David: How do the project goals feed into your process, Niko?
NM: Today's setup creates a weird conflict of interest where people who are accountable to their employers can end up trying to avoid talking about that because it can see "slimy". Putting this forward as a "good thing" within the project can help us more clearly discuss this.
tmandry: I don't see how, e.g., I would be able to put effort into writing a good document about using async Rust today. Probably because I want to revise so much of that experience. But I can see writing about the future.
Adam: The project doesn't always do a good job at putting forward what it wants so that other people can help.
NM: The unfunded goal is the way of saying that.
TC: Eric, to your specific point about NRC being willing to do this with funding, this could be an interesting case for what we had discussed about using project goals as a mechanism by which teams could allocate money derived from the Foundation (e.g., to direct the Foundation to contract out for a specific body of work identified in the project goal).
eholk: There are certainly mixed feelings that I have here.
NM: There are many hesitations I have here. Mostly, I'm concerned about the project allocating money in this way due to the fact that we'd need a fair process here.
eholk: In the council, for these hiring decisions, this would be more about us telling the Foundation what we want and to make it happen, as they would handle the HR. Another aspect is that the Foundation is good about going out and attracting funding if they know what we want them to attract funding for. We discussed this, e.g., at the leads summit.
(The meeting ended here.)
---
## Poll model scales poorly sometimes
> Complex futures like FuturesUnordered or joining a large number of tasks can have very poor performance because of the limits of the poll API.
Yosh: Can you give an example of this? I know I've struggled with getting some implementations right (e.g. futures-concurrency), but I'm not sure if this is inherent to the model?
Yosh: I guess the broader question is: does this point to deficiencies in the core model?