-
Notifications
You must be signed in to change notification settings - Fork 301
Author "Async Closures MVP: Call for Testing!" #1377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a66f099
Add async closures call for testing post
compiler-errors 1ee9c46
Feat
compiler-errors c99a367
Fix inconsistent caps
compiler-errors 6e806ed
Apply TC's tweaks
compiler-errors 406e7a6
Update posts/inside-rust/2024-08-09-async-closures-call-for-testing.md
compiler-errors f0c1eb4
Flesh a few more examples out
compiler-errors d630123
Add an example of a higher ranked closure returning boxed future
compiler-errors 757977a
Last tweaks
compiler-errors 4e4c18f
Update posts/inside-rust/2024-08-09-async-closures-call-for-testing.md
compiler-errors File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
posts/inside-rust/2024-08-09-async-closures-call-for-testing.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
--- | ||
layout: post | ||
title: "Async Closures MVP: Call for Testing!" | ||
author: Michael Goulet | ||
team: The Async Working Group <https://www.rust-lang.org/governance/wgs/wg-async> | ||
--- | ||
|
||
The async working group is excited to announce that [RFC 3668] "Async Closures" was recently approved by the Lang team. In this post, we want to briefly motivate why they exist, explain their current shortcomings, and most importantly, announce a call for testing them on nightly Rust. | ||
|
||
## The backstory | ||
|
||
Async closures were originally proposed in [RFC 2394](https://rust-lang.github.io/rfcs/2394-async_await.html#async--closures) which introduced `async`/`await` to the language. Simple handling of async closures has existed since async-await was implemented [soon thereafter](https://github.com/rust-lang/rust/pull/51580), but until recently async closures simply desugared into closures that returned async blocks: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
let x = async || {}; | ||
|
||
// ...is just sugar for: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let x = || { async {} }; | ||
``` | ||
|
||
This had a fundamental limitation that it's impossible to express a closure that returns a future that borrows captured state. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Somewhat relatedly, on the callee side, when users want to take an async closure as an argument, they must express that as a bound of two different generic types, or use boxing: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
fn async_callback<F, Fut>(callback: F) | ||
where | ||
F: FnOnce() -> Fut, | ||
Fut: Future<Output = String>; | ||
|
||
// Or: | ||
fn async_callback_boxed<F, Fut>(callback: F) | ||
where | ||
F: FnOnce() -> Pin<Box<dyn Future<Output = String>>>; | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
This led to an additional limitation, that it's impossible to express higher ranked async fn bounds without boxing, since a higher ranked trait bound on `F` cannot lead to a higher-ranked type for `Fut`. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
These limitations were detailed in [Niko's blog post on async closures and lending](https://smallcultfollowing.com/babysteps/blog/2023/05/09/giving-lending-and-async-closures/#async-closures-are-a-lending-pattern), and later in compiler-errors's blog post on [why async closures are the way they are](https://hackmd.io/@compiler-errors/async-closures). | ||
|
||
## OK, so how does [RFC 3668] help? | ||
|
||
Recent [implementation work](https://github.com/rust-lang/rust/pull/120361) has focused on reimplementing async closures to be lending, and to design a set of async fn traits to use in parallel. While async closures already existed as syntax, it introduced a new family of async fn traits which are implemented by async closures (and all other callable types which return futures), and which can be written like: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
fn test<F>(callback: F) | ||
where | ||
// Either: | ||
async Fn(Arg, Arg) -> Ret | ||
// or: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
AsyncFn(Arg, Arg) -> Ret | ||
``` | ||
|
||
(It's currently an [open question](https://github.com/rust-lang/rust/issues/128129) exactly how to spell this bound, so both syntaxes are implemented in parallel.) | ||
|
||
RFC 3668 motivates this implementation work in detail, confirming that we need first-class async closures and async fn traits which allow us to express the *lending* capability of async closures -- read the RFC if you're interested in the whole story! | ||
|
||
## So how do I help? | ||
|
||
We'd love for you to test out these new features! First, on a recently-updated nightly compiler, enable the `#![feature(async_closure)]` (the feature is not pluralized). | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
For Rust users, in general, async closures are designed to be drop-in compatible with closures returning async blocks: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
// Instead of writing: | ||
takes_async_callback(|arg| async { | ||
// Do things here... | ||
}); | ||
|
||
// Write this: | ||
takes_async_callback(async |arg| { | ||
// Do things here... | ||
}); | ||
``` | ||
|
||
And on the callee side, users should write async fn trait bounds instead of writing "regular" fn trait bounds that return futures: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rust | ||
// Instead of writing: | ||
fn doesnt_exactly_take_an_async_closure<F, Fut>() | ||
where | ||
F: FnOnce() -> Fut, | ||
Fut: Future<Output = String> | ||
{ todo!() } | ||
|
||
// Write this: | ||
fn takes_an_async_closure<F: async FnOnce() -> String>() { todo!() } | ||
// or this: | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn takes_an_async_closure<F: AsyncFnOnce() -> String>() { todo!() } | ||
``` | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Shortcomings interacting with the async ecosystem | ||
|
||
If you're going to try to rewrite your async projects, there are a few shortcomings you may want to be aware of. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### You can't directly name the output future | ||
|
||
When you name an async callable bound with the *old* style, before first-class async fn trait bounds, then as a side-effect of needing to use two type parameters, you can put additional bounds (e.g. `+ Send` or `+ 'static`) on the `Future` part of the bound, like: | ||
|
||
```rust | ||
fn async_callback<F, Fut>() | ||
where | ||
F: FnOnce() -> Fut, | ||
Fut: Future<Output = String> + Send + 'static | ||
{ todo!() } | ||
``` | ||
|
||
There isn't currently a way to put similar bounds on the future returned by calling an async closure, so if you need to constrain your callback futures like this, then you won't be able to use async closures just yet. | ||
|
||
We expect to support this in the medium/long term via a [return-type-notation syntax](https://rust-lang.github.io/rfcs/3668-async-closures.html#interaction-with-return-type-notation-naming-the-future-returned-by-calling). | ||
|
||
### Subtle differences in closure signature inference | ||
|
||
Passing an async closure to a generic `impl Fn(A, B) -> C` bound may not always eagerly infer the closure's arguments to `A` and `B`, leading to strange type errors on occasion. For an example of this, see [`rust-lang/rust#127781`](https://github.com/rust-lang/rust/issues/127781). | ||
|
||
We expect to improve async closure signature inference moving forward. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Async closures can't be coerced to `fn()` pointers | ||
|
||
Some libraries take their callbacks as function *pointers* (`fn()`) rather than generics. Async closures don't currently implement the same coercion from closure to `fn() -> ...`. Some libraries may mitigate this problem by adapting their API to take generic `impl Fn()` instead of `fn()` pointers as an argument. | ||
|
||
We don't expect to implement this coercion unless there's a particularly good reason to support, since they can always be replaced with an inner function item. | ||
compiler-errors marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[RFC 3668]: https://rust-lang.github.io/rfcs/3668-async-closures.html |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.