--- title: "Design meeting 2024-07-22: Async closures" tags: ["T-lang", "design-meeting", "minutes"] date: 2024-07-22 discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202024-07-22 url: https://hackmd.io/X3qs1S9SSwmAKnbsc1rbxg --- ### Async Fn Sugar `Fn` traits and async are each complex in isolation. Syntax sugar exists for both of them to bridge this complexity gap and to draw syntactic symmetry between the sugar and how it relates to underlying concepts in the language. For `Fn` traits, we want to draw a parallel between `fn` items and pointers, and `Fn` trait bounds via parenthesized argument lists and usage of the return arrow `->`, and so we've settled on parenthesized generic sugar. For async, we want as much as possible for people to "just write `async`" in places that they want to be able to use `.await`. This means: * Users can add `async` to their `fn function()`s in order to make them async. * Users can add `async` to their closures in order to make them async. * Users can add `async` to their `Fn` trait bounds. <-- **this** is the case that is under consideration in this meeting. We intentionally add `async` on top of the parenthesized generic sugar because that sugar is already well established: users are familiar with the parallel it draws between other `fn`-like concepts in the language. While it's useful to think about extensions towards a general `async` trait bound modifier, deciding to use `async` in `Fn` trait bounds does *not* require us to add `async` anywhere else -- the parallel we're drawing here is between `async fn` and `async Fn`, not between `async Fn` and arbitrary `async Trait`s, and I believe this alone carries the weight of the feature. One alternative way of thinking about this is not as a general trait bound modifier, but making `async` consistent as a `fn` modifier, but in trait bound position. Risks: * `async Fn` may draw users' attention towards the lack `async Trait` in general. I don't believe this is a concern, since `Fn` traits are *special* already. They are the only traits that allow parenthesized generic sugar; this specialness is a consequence of functions and closures being first-class in Rust, and we're leaning into users' familiarity with them as first-class language concepts in this proposal. * There is a risk that `async Fn` might have different (user observable) semantics than some future async bound modifier. An argument could be made that by using `AsyncFn` as the trait name, we're saving space by mitigating any issues down the road if we choose a desugaring for `async Trait` that is inconsistent with the (user observable) details of `AsyncFn`. I don't believe that this is actually the case, though -- inconsistency will still arise if we chose an `async Trait` desugaring that results in `async Fn` behaving differently than `AsyncFn`, so we're not saving any space by calling the trait `AsyncFn`. Other considerations: * `LendingFn*` compatibility - Using a first-class syntax allows more flexibility on the implementation side. This was argued at length by oli-obk in a thread on the RFC, but tl;dr is that trait aliases are not currently sufficient to move onto `LendingFn` in the future, so we'd have to add either new language support or a name-resolution hack to support this case. It's worth keeping this in mind, since not all ways of "*saving space*" are zero-cost. * There is a risk of a trait bound naming blow-up if we ever gain `async gen` or `gen` closures and settle on `AsyncFn*` as the naming scheme. Extending these language features (e.g. `async gen` closures) becomes less intuitive if users must learn that `AsyncGenFn()` is how it needs to be spelled in trait bounds. --- # Discussion ## Attendance - People: TC, CE, nikomatsakis, tmandry, eholk, pnkfelix ## Meeting roles - Minutes, driver: TC