Skip to content
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

Describe async closures #1692

Merged
merged 15 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
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
15 changes: 13 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,18 @@ 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.

> **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
26 changes: 24 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 closures can be coerced to function pointers (e.g., `fn()`)
ehuss marked this conversation as resolved.
Show resolved Hide resolved
with the matching signature.

```rust
Expand All @@ -504,7 +509,24 @@ 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* if it has either of the following properties:

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

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

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
Loading