From db097d7968178b28d113ace2a9d49338e6092cc4 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 4 Dec 2024 19:03:27 +0000 Subject: [PATCH 01/15] Describe async closures --- src/expressions/closure-expr.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index 103f74795..65cab50a9 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -2,6 +2,7 @@ > **Syntax**\ > _ClosureExpression_ :\ +>    `async`?\ >    `move`?\ >    ( `||` | `|` _ClosureParameters_? `|` )\ >    ([_Expression_] | `->` [_TypeNoBounds_] [_BlockExpression_]) @@ -13,7 +14,7 @@ >    [_OuterAttribute_]\* [_PatternNoTopAlt_] ( `:` [_Type_] )? 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 +30,19 @@ 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 + +When a closure is marked with the `async` keyword, it is allowed to `await` futures in its body. +Calling the closure evaluates to a future that corresponds to the computation of the body of the closure. +This closure implements `AsyncFn`, `AsyncFnMut`, and `AsyncFnOnce` depending on the use of the captured variables in its body. +It may also implement `Fn`, `FnMut`, and `FnOnce` if the future it returns does not borrow any captured variables from the closure itself. + +> **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. From e7a43960e06dc2bfd94873584fe1872a7f58d4e7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:07:07 -0800 Subject: [PATCH 02/15] Update std-links to default to 2024 --- mdbook-spec/src/std_links.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdbook-spec/src/std_links.rs b/mdbook-spec/src/std_links.rs index 28ca6ba51..e5b9448d1 100644 --- a/mdbook-spec/src/std_links.rs +++ b/mdbook-spec/src/std_links.rs @@ -247,7 +247,7 @@ fn run_rustdoc(tmp: &TempDir, chapter_links: &HashMap<&PathBuf, Vec>>) 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() From 2cccea5f325f356f0f870b2a474f5b7c1d274d96 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:07:29 -0800 Subject: [PATCH 03/15] Async closures cannot be coerced to a function pointer --- src/types/function-pointer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/function-pointer.md b/src/types/function-pointer.md index d7950b159..21a398265 100644 --- a/src/types/function-pointer.md +++ b/src/types/function-pointer.md @@ -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 From a81141dfccfc53eefdac3b738e8084d6ccd9d40b Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:08:37 -0800 Subject: [PATCH 04/15] Note that `async` closure keyword is edition-specific Because we do not have a way to really express partial grammars that are edition-specific, we must resort to footnotes. --- src/expressions/closure-expr.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index 65cab50a9..0baf97f2c 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -2,7 +2,7 @@ > **Syntax**\ > _ClosureExpression_ :\ ->    `async`?\ +>    `async`[^cl-async-edition]?\ >    `move`?\ >    ( `||` | `|` _ClosureParameters_? `|` )\ >    ([_Expression_] | `->` [_TypeNoBounds_] [_BlockExpression_]) @@ -12,6 +12,8 @@ > > _ClosureParam_ :\ >    [_OuterAttribute_]\* [_PatternNoTopAlt_] ( `:` [_Type_] )? +> +> [^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 `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*. From 927ab5ea56c542cb46c021a217c4b61fd2c71dc3 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:09:14 -0800 Subject: [PATCH 05/15] `await` can also be used in async closures --- src/expressions/await-expr.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/expressions/await-expr.md b/src/expressions/await-expr.md index ae129890f..2d233f348 100644 --- a/src/expressions/await-expr.md +++ b/src/expressions/await-expr.md @@ -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 From 721b5cb6e0308bf2e2b8d4fd0f8d7c279a8592fb Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:10:26 -0800 Subject: [PATCH 06/15] Note that call expressions also work with the new AsyncFn* types --- src/expressions/call-expr.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/expressions/call-expr.md b/src/expressions/call-expr.md index 64df58ce4..fc196b7a6 100644 --- a/src/expressions/call-expr.md +++ b/src/expressions/call-expr.md @@ -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. From 8f2cf33f39220ba80ad0e2c0d87ee7d78faefbb7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 14:18:38 -0800 Subject: [PATCH 07/15] Slight rewording of async closure expr Minor rewording with an eye towards marking these with individual rules. --- src/expressions/closure-expr.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index 0baf97f2c..844fedd33 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -38,10 +38,13 @@ The closure type implements [`Send`] and [`Sync`] if the type of every captured ## Async closures -When a closure is marked with the `async` keyword, it is allowed to `await` futures in its body. -Calling the closure evaluates to a future that corresponds to the computation of the body of the closure. -This closure implements `AsyncFn`, `AsyncFnMut`, and `AsyncFnOnce` depending on the use of the captured variables in its body. -It may also implement `Fn`, `FnMut`, and `FnOnce` if the future it returns does not borrow any captured variables from the closure itself. +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. + +Async closures implement [`AsyncFn`], [`AsyncFnMut`], and [`AsyncFnOnce`] depending on the use of the captured variables in its body. + +Async closures may also implement [`Fn`], [`FnMut`], and [`FnOnce`] if the future it returns does not borrow any captured variables from the closure itself. > **Edition differences**: Async closures are only available beginning with Rust 2018. From 10240ea6eab635f703ba64c85059b02c885d95bc Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 21:00:46 -0800 Subject: [PATCH 08/15] Clarify that Async family is not dyn-compatible --- src/items/traits.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/items/traits.md b/src/items/traits.md index dd315e440..0d0a9ecbd 100644 --- a/src/items/traits.md +++ b/src/items/traits.md @@ -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 From 502e8afdbf69309beaff19b19e7611dff5a02a13 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 21:01:10 -0800 Subject: [PATCH 09/15] Another clarification that async closures cannot be coerced to a fn pointer --- src/types/closure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/closure.md b/src/types/closure.md index d46d7f7ae..81f59889f 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -491,7 +491,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()`) with the matching signature. ```rust From ff8c4eb284969983730f53a5782d75860047f640 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 21:02:21 -0800 Subject: [PATCH 10/15] Further elaborate on async closure trait implementation --- src/expressions/closure-expr.md | 4 ---- src/types/closure.md | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index 844fedd33..df06f4d87 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -42,10 +42,6 @@ Closures marked with the `async` keyword indicate that they are asynchronous in 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. -Async closures implement [`AsyncFn`], [`AsyncFnMut`], and [`AsyncFnOnce`] depending on the use of the captured variables in its body. - -Async closures may also implement [`Fn`], [`FnMut`], and [`FnOnce`] if the future it returns does not borrow any captured variables from the closure itself. - > **Edition differences**: Async closures are only available beginning with Rust 2018. ## Example diff --git a/src/types/closure.md b/src/types/closure.md index 81f59889f..0fe913685 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -504,7 +504,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] From 1106780cdcc730ae086608863a12dcbcd19e77be Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 11 Dec 2024 21:07:55 -0800 Subject: [PATCH 11/15] Specify that async closures capture their inputs --- src/types/closure.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/types/closure.md b/src/types/closure.md index 0fe913685..47e5f2bf2 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -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] From 73832d340e8c182306ee3ae767d38bb90a9b1803 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 12 Dec 2024 15:25:32 -0800 Subject: [PATCH 12/15] Add a basic example for a closure expression --- src/expressions/closure-expr.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/expressions/closure-expr.md b/src/expressions/closure-expr.md index df06f4d87..abb3c8e9a 100644 --- a/src/expressions/closure-expr.md +++ b/src/expressions/closure-expr.md @@ -42,6 +42,20 @@ Closures marked with the `async` keyword indicate that they are asynchronous in 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 From adef05f49366eeafb53fe67ea7008f8caa28d3e6 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 12 Dec 2024 15:26:53 -0800 Subject: [PATCH 13/15] Some tweaks to the async Fn* wording Trying to avoid some confusion. --- src/types/closure.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index 47e5f2bf2..3c41c5505 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -516,12 +516,12 @@ 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`] 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 capture is mutable. +- 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 the future, then [`FnMut`] and [`Fn`] are *not* implemented. +If the async closure is lending to its `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. From 5a8b949d9585222e158a0c6122a593bf393bb1a7 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 12 Dec 2024 15:27:46 -0800 Subject: [PATCH 14/15] Add examples for async's Fn* rules These are pretty subtle, and hard to follow. An example helps a lot. I really wanted to include a little bit more commentary on *why* these apply, but I think that gets into some fairly complicated reasoning that I'm not prepared to distill. --- src/types/closure.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/types/closure.md b/src/types/closure.md index 3c41c5505..a240036b0 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -523,6 +523,48 @@ The [`Future`] returned by the async closure has similar capturing characteristi If the async closure is lending to its `Future`, then [`FnMut`] and [`Fn`] are *not* implemented. +> **Example**: The first clause for a mutable capture can be illustrated with the following: +> +> ```rust,compile_fail +> fn takes_callback(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(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(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. From 8eb71da97d2a8ffa996e5ab9deadb89a8627dd7e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 12 Dec 2024 15:57:03 -0800 Subject: [PATCH 15/15] Async clarifications from compiler-errors --- src/types/closure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/closure.md b/src/types/closure.md index a240036b0..2fc59276d 100644 --- a/src/types/closure.md +++ b/src/types/closure.md @@ -496,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. Non-async closures 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 @@ -521,7 +521,7 @@ The [`Future`] returned by the async closure has similar capturing characteristi - 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. +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: >