Skip to content

Commit

Permalink
Merge pull request #1692 from compiler-errors/async-closures
Browse files Browse the repository at this point in the history
Describe async closures
  • Loading branch information
ehuss authored Dec 14, 2024
2 parents d6d24b9 + 8eb71da commit 183dd4b
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 8 deletions.
2 changes: 1 addition & 1 deletion mdbook-spec/src/std_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion src/expressions/await-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/expressions/call-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
29 changes: 27 additions & 2 deletions src/expressions/closure-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_])
Expand All @@ -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].

Expand All @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions src/items/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 66 additions & 2 deletions src/types/closure.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/types/function-pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 183dd4b

Please sign in to comment.