Skip to content

Describe async closures #1692

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 15 commits into from
Dec 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mdbook-spec/src/std_links.rs
Original file line number Diff line number Diff line change
@@ -247,7 +247,7 @@ fn run_rustdoc(tmp: &TempDir, chapter_links: &HashMap<&PathBuf, Vec<Link<'_>>>)
fs::write(&src_path, &src).unwrap();
let rustdoc = std::env::var("RUSTDOC").unwrap_or_else(|_| "rustdoc".into());
let output = Command::new(rustdoc)
.arg("--edition=2021")
.arg("--edition=2024")
.arg(&src_path)
.current_dir(tmp.path())
.output()
3 changes: 2 additions & 1 deletion src/expressions/await-expr.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ An `await` expression is a syntactic construct for suspending a computation
provided by an implementation of `std::future::IntoFuture` until the given
future is ready to produce a value.
The syntax for an await expression is an expression with a type that implements the [`IntoFuture`] trait, called the *future operand*, then the token `.`, and then the `await` keyword.
Await expressions are legal only within an [async context], like an [`async fn`] or an [`async` block].
Await expressions are legal only within an [async context], like an [`async fn`], [`async` closure], or [`async` block].

More specifically, an await expression has the following effect.

@@ -48,6 +48,7 @@ The variable `current_context` refers to the context taken from the async enviro

[_Expression_]: ../expressions.md
[`async fn`]: ../items/functions.md#async-functions
[`async` closure]: closure-expr.md#async-closures
[`async` block]: block-expr.md#async-blocks
[`Context`]: std::task::Context
[`future::poll`]: std::future::Future::poll
8 changes: 7 additions & 1 deletion src/expressions/call-expr.md
Original file line number Diff line number Diff line change
@@ -10,7 +10,13 @@
A *call expression* calls a function.
The syntax of a call expression is an expression, called the *function operand*, followed by a parenthesized comma-separated list of expression, called the *argument operands*.
If the function eventually returns, then the expression completes.
For [non-function types], the expression `f(...)` uses the method on one of the [`std::ops::Fn`], [`std::ops::FnMut`] or [`std::ops::FnOnce`] traits, which differ in whether they take the type by reference, mutable reference, or take ownership respectively.

For [non-function types], the expression `f(...)` uses the method on one of the following traits based on the function operand:

- [`Fn`] or [`AsyncFn`] --- shared reference.
- [`FnMut`] or [`AsyncFnMut`] --- mutable reference.
- [`FnOnce`] or [`AsyncFnOnce`] --- value.

An automatic borrow will be taken if needed.
The function operand will also be [automatically dereferenced] as required.

29 changes: 27 additions & 2 deletions src/expressions/closure-expr.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

> **<sup>Syntax</sup>**\
> _ClosureExpression_ :\
> &nbsp;&nbsp; `async`[^cl-async-edition]<sup>?</sup>\
> &nbsp;&nbsp; `move`<sup>?</sup>\
> &nbsp;&nbsp; ( `||` | `|` _ClosureParameters_<sup>?</sup> `|` )\
> &nbsp;&nbsp; ([_Expression_] | `->` [_TypeNoBounds_]&nbsp;[_BlockExpression_])
@@ -11,9 +12,11 @@
>
> _ClosureParam_ :\
> &nbsp;&nbsp; [_OuterAttribute_]<sup>\*</sup> [_PatternNoTopAlt_]&nbsp;( `:` [_Type_] )<sup>?</sup>
>
> [^cl-async-edition]: The `async` qualifier is not allowed in the 2015 edition.

A *closure expression*, also known as a lambda expression or a lambda, defines a [closure type] and evaluates to a value of that type.
The syntax for a closure expression is an optional `move` keyword, then a pipe-symbol-delimited (`|`) comma-separated list of [patterns], called the *closure parameters* each optionally followed by a `:` and a type, then an optional `->` and type, called the *return type*, and then an expression, called the *closure body operand*.
The syntax for a closure expression is an optional `async` keyword, an optional `move` keyword, then a pipe-symbol-delimited (`|`) comma-separated list of [patterns], called the *closure parameters* each optionally followed by a `:` and a type, then an optional `->` and type, called the *return type*, and then an expression, called the *closure body operand*.
The optional type after each pattern is a type annotation for the pattern.
If there is a return type, the closure body must be a [block].

@@ -29,10 +32,32 @@ This is often used to ensure that the closure's lifetime is `'static`.

## Closure trait implementations

Which traits the closure type implement depends on how variables are captured and the types of the captured variables.
Which traits the closure type implement depends on how variables are captured, the types of the captured variables, and the presence of `async`.
See the [call traits and coercions] chapter for how and when a closure implements `Fn`, `FnMut`, and `FnOnce`.
The closure type implements [`Send`] and [`Sync`] if the type of every captured variable also implements the trait.

## Async closures

Closures marked with the `async` keyword indicate that they are asynchronous in an analogous way to an [async function][items.fn.async].

Calling the async closure does not perform any work, but instead evaluates to a value that implements [`Future`] that corresponds to the computation of the body of the closure.

```rust
async fn takes_async_callback(f: impl AsyncFn(u64)) {
f(0).await;
f(1).await;
}

async fn example() {
takes_async_callback(async |i| {
core::future::ready(i).await;
println!("done with {i}.");
}).await;
}
```

> **Edition differences**: Async closures are only available beginning with Rust 2018.

## Example

In this example, we define a function `ten_times` that takes a higher-order function argument, and we then call it with a closure expression as an argument, followed by a closure expression that moves values from its environment.
3 changes: 3 additions & 0 deletions src/items/traits.md
Original file line number Diff line number Diff line change
@@ -120,6 +120,9 @@ r[items.traits.dyn-compatible.associated-functions]
* Explicitly non-dispatchable functions require:
* Have a `where Self: Sized` bound (receiver type of `Self` (i.e. `self`) implies this).

r[items.traits.dyn-compatible.async-traits]
* The [`AsyncFn`], [`AsyncFnMut`], and [`AsyncFnOnce`] traits are not dyn-compatible.

> **Note**: This concept was formerly known as *object safety*.

```rust
68 changes: 66 additions & 2 deletions src/types/closure.md
Original file line number Diff line number Diff line change
@@ -94,6 +94,11 @@ let c = || {
};
```

### Async input capture

r[type.closure.async.input]
Async closures always capture all input arguments, regardless of whether or not they are used within the body.

## Capture Precision

r[type.closure.capture.precision.capture-path]
@@ -491,7 +496,7 @@ r[type.closure.call.fn]

r[type.closure.non-capturing]
*Non-capturing closures* are closures that don't capture anything from their
environment. They can be coerced to function pointers (e.g., `fn()`)
environment. Non-async, non-capturing closures can be coerced to function pointers (e.g., `fn()`)
with the matching signature.

```rust
@@ -504,7 +509,66 @@ let bo: Binop = add;
x = bo(5,7);
```

## Other traits
### Async closure traits

r[type.closure.async.traits]

r[type.closure.async.traits.fn-family]
Async closures have a further restriction of whether or not they implement [`FnMut`] or [`Fn`].

The [`Future`] returned by the async closure has similar capturing characteristics as a closure. It captures place expressions from the async closure based on how they are used. The async closure is said to be *lending* to its [`Future`] if it has either of the following properties:

- The `Future` includes a mutable capture.
- The async closure captures by value, except when the value is accessed with a dereference projection.

If the async closure is lending to its `Future`, then [`FnMut`] and [`Fn`] are *not* implemented. [`FnOnce`] is always implemented.

> **Example**: The first clause for a mutable capture can be illustrated with the following:
>
> ```rust,compile_fail
> fn takes_callback<Fut: Future>(c: impl FnMut() -> Fut) {}
>
> fn f() {
> let mut x = 1i32;
> let c = async || {
> x = 2; // x captured with MutBorrow
> };
> takes_callback(c); // ERROR: async closure does not implement `FnMut`
> }
> ```
>
> The second clause for a regular value capture can be illustrated with the following:
>
> ```rust,compile_fail
> fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {}
>
> fn f() {
> let x = &1i32;
> let c = async move || {
> let a = x + 2; // x captured ByValue
> };
> takes_callback(c); // ERROR: async closure does not implement `Fn`
> }
> ```
>
> The exception of the the second clause can be illustrated by using a dereference, which does allow `Fn` and `FnMut` to be implemented:
>
> ```rust
> fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {}
>
> fn f() {
> let x = &1i32;
> let c = async move || {
> let a = *x + 2;
> };
> takes_callback(c); // OK: implements `Fn`
> }
> ```

r[type.closure.async.traits.async-family]
Async closures implement [`AsyncFn`], [`AsyncFnMut`], and [`AsyncFnOnce`] in an analogous way as regular closures implement [`Fn`], [`FnMut`], and [`FnOnce`]; that is, depending on the use of the captured variables in its body.

### Other traits

r[type.closure.traits]

2 changes: 1 addition & 1 deletion src/types/function-pointer.md
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ Function pointer types, written using the `fn` keyword, refer to a function
whose identity is not necessarily known at compile-time.

r[type.fn-pointer.coercion]
They can be created via a coercion from both [function items] and non-capturing [closures].
They can be created via a coercion from both [function items] and non-capturing, non-async [closures].

r[type.fn-pointer.qualifiers]
The `unsafe` qualifier indicates that the type's value is an [unsafe