From a66f099d2e2285e39145bd214b90d74671a4c8d7 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 9 Aug 2024 15:31:35 -0400 Subject: [PATCH 1/9] Add async closures call for testing post --- ...4-08-09-async-closures-call-for-testing.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 posts/inside-rust/2024-08-09-async-closures-call-for-testing.md diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md new file mode 100644 index 000000000..df872c76d --- /dev/null +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -0,0 +1,124 @@ +--- +layout: post +title: "Async Closures MVP: Call for Testing!" +author: Michael Goulet +team: The Async Working Group +--- + +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: + +```rust +let x = async || {}; + +// ...is just sugar for: +let x = || { async {} }; +``` + +This had a fundamental limitation that it's impossible to express a closure that returns a future that borrows captured state. + +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: + +```rust +fn async_callback(callback: F) +where + F: FnOnce() -> Fut, + Fut: Future; + +// Or: +fn async_callback_boxed(callback: F) +where + F: FnOnce() -> Pin>>; +``` + +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`. + +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: + +```rust +fn test(callback: F) +where + // Either: + async Fn(Arg, Arg) -> Ret + // or: + 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! + +For Rust users, in general, async closures are designed to be drop-in compatible with closures returning async blocks: + +```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: + +```rust +// Instead of writing: +fn doesnt_exactly_take_an_async_closure() +where + F: FnOnce() -> Fut, + Fut: Future +{ todo!() } + +// Write this: +fn takes_an_async_closure String>() { todo!() } +// or this: +fn takes_an_async_closure String>() { todo!() } +``` + +## 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. + +### 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() +where + F: FnOnce() -> Fut, + Fut: Future + 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. + +### 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. + +[RFC 3668]: https://rust-lang.github.io/rfcs/3668-async-closures.html \ No newline at end of file From 1ee9c46d2a9f95d29a73131f916bfc7377147043 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 9 Aug 2024 15:32:53 -0400 Subject: [PATCH 2/9] Feat --- posts/inside-rust/2024-08-09-async-closures-call-for-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index df872c76d..3034d4d04 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -57,7 +57,7 @@ RFC 3668 motivates this implementation work in detail, confirming that we need f ## So how do I help? -We'd love for you to test out these new features! +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). For Rust users, in general, async closures are designed to be drop-in compatible with closures returning async blocks: From c99a36781d70073299fbeb98893c039722e8ee26 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 9 Aug 2024 15:36:08 -0400 Subject: [PATCH 3/9] Fix inconsistent caps --- .../inside-rust/2024-08-09-async-closures-call-for-testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index 3034d4d04..e89f77c0e 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -47,7 +47,7 @@ fn test(callback: F) where // Either: async Fn(Arg, Arg) -> Ret - // or: + // Or: AsyncFn(Arg, Arg) -> Ret ``` @@ -85,7 +85,7 @@ where // Write this: fn takes_an_async_closure String>() { todo!() } -// or this: +// Or this: fn takes_an_async_closure String>() { todo!() } ``` From 6e806ed4102aba430bc539635d7353bc35bf945b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 9 Aug 2024 16:54:18 -0400 Subject: [PATCH 4/9] Apply TC's tweaks Co-authored-by: Travis Cross --- ...4-08-09-async-closures-call-for-testing.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index e89f77c0e..0072f8164 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -5,20 +5,20 @@ author: Michael Goulet team: The Async Working Group --- -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 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 async closures 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: +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 in nightly 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: ```rust let x = async || {}; -// ...is just sugar for: +// ...was just sugar for: let x = || { async {} }; ``` -This had a fundamental limitation that it's impossible to express a closure that returns a future that borrows captured state. +This had a fundamental limitation that it was impossible to express a closure that returns a future that borrows captured state. 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: @@ -34,13 +34,13 @@ where F: FnOnce() -> Pin>>; ``` -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`. +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`. 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: +Recent [work](https://github.com/rust-lang/rust/pull/120361) has focused on reimplementing async closures to be lending and designing a set of async fn traits. While async closures already existed as syntax, this work introduced a new family of async fn traits which are implemented by async closures (and all other callable types which return futures). They can be written like: ```rust fn test(callback: F) @@ -57,9 +57,9 @@ RFC 3668 motivates this implementation work in detail, confirming that we need f ## 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). +We'd love for you to test out these new features! First, on a recently-updated nightly compiler, enable `#![feature(async_closure)]` (note that, for historical reasons, this feature name is not pluralized). -For Rust users, in general, async closures are designed to be drop-in compatible with closures returning async blocks: +Async closures are designed to be drop-in compatible (in almost all cases) with closures returning async blocks: ```rust // Instead of writing: @@ -73,7 +73,7 @@ takes_async_callback(async |arg| { }); ``` -And on the callee side, users should write async fn trait bounds instead of writing "regular" fn trait bounds that return futures: +And on the callee side, write async fn trait bounds instead of writing "regular" fn trait bounds that return futures: ```rust // Instead of writing: @@ -91,7 +91,7 @@ fn takes_an_async_closure String>() { todo!() } ## 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. +If you're going to try to rewrite your async projects, there are a few shortcomings to be aware of. ### You can't directly name the output future @@ -113,12 +113,12 @@ We expect to support this in the medium/long term via a [return-type-notation sy 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. +We expect to improve async closure signature inference as we move forward. ### 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. +We don't expect to implement this coercion unless there's a particularly good reason to support it, since this can always be handled manually by the caller with an inner function item. [RFC 3668]: https://rust-lang.github.io/rfcs/3668-async-closures.html \ No newline at end of file From 406e7a6bb867a5962f767cc24dd98e1f8c3ab1a0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 11 Aug 2024 21:54:43 -0400 Subject: [PATCH 5/9] Update posts/inside-rust/2024-08-09-async-closures-call-for-testing.md The only case this can't happen is when inference infers an unnameable type --- posts/inside-rust/2024-08-09-async-closures-call-for-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index 0072f8164..066844e83 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -119,6 +119,6 @@ We expect to improve async closure signature inference as we move forward. 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 it, since this can always be handled manually by the caller with an inner function item. +We don't expect to implement this coercion unless there's a particularly good reason to support it, since this can usually be handled manually by the caller by using an inner function item. [RFC 3668]: https://rust-lang.github.io/rfcs/3668-async-closures.html \ No newline at end of file From f0c1eb46c0ef11b2cb75eb35a01dd07403e96de8 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 14 Aug 2024 12:44:05 -0400 Subject: [PATCH 6/9] Flesh a few more examples out --- ...4-08-09-async-closures-call-for-testing.md | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index 066844e83..0e77b9dbd 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -46,9 +46,9 @@ Recent [work](https://github.com/rust-lang/rust/pull/120361) has focused on reim fn test(callback: F) where // Either: - async Fn(Arg, Arg) -> Ret + async Fn(Arg, Arg) -> Ret, // Or: - AsyncFn(Arg, Arg) -> Ret + 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.) @@ -77,16 +77,31 @@ And on the callee side, write async fn trait bounds instead of writing "regular" ```rust // Instead of writing: -fn doesnt_exactly_take_an_async_closure() +fn doesnt_exactly_take_an_async_closure(callback: F) where F: FnOnce() -> Fut, Fut: Future { todo!() } // Write this: -fn takes_an_async_closure String>() { todo!() } +fn takes_an_async_closure String>(callback: F) { todo!() } // Or this: -fn takes_an_async_closure String>() { todo!() } +fn takes_an_async_closure String>(callback: F) { todo!() } +``` + +Or if you're emulating a higher-ranked async closure with boxing: + +```rust +// Instead of writing: +fn higher_ranked(callback: F) +where + F: Fn(&Arg) -> Pin + '_>> +{ todo!() } + +// Write this: +fn higher_ranked { todo!() } +// Or this: +fn higher_ranked { todo!() } ``` ## Shortcomings interacting with the async ecosystem @@ -98,7 +113,7 @@ If you're going to try to rewrite your async projects, there are a few shortcomi 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() +fn async_callback(callback: F) where F: FnOnce() -> Fut, Fut: Future + Send + 'static @@ -119,6 +134,26 @@ We expect to improve async closure signature inference as we move forward. 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 it, since this can usually be handled manually by the caller by using an inner function item. +We don't expect to implement this coercion unless there's a particularly good reason to support it, since this can usually be handled manually by the caller by using an inner function item, or by using an `Fn` bound instead: for example: + +```rust +fn needs_fn_pointer>(callback: fn() -> T) { todo!() } + +fn main() { + // Instead of writing: + needs_fn_pointer(async || { todo!() }); + // Since async closures don't currently support coercion to `fn() -> ...`. + + // You can use an inner async fn item: + async fn callback() { todo!() } + needs_fn_pointer(callback); +} + +// Or if you don't need to take *exactly* a function pointer, +// you can rewrite `needs_fn_pointer` like: +fn needs_fn_pointer(callback: impl async Fn()) { todo!() } +// Or with `AsyncFn`: +fn needs_fn_pointer(callback: impl AsyncFn()) { todo!() } +``` [RFC 3668]: https://rust-lang.github.io/rfcs/3668-async-closures.html \ No newline at end of file From d630123a8b8795c1f07161ad6ff18cf56ea68a78 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 14 Aug 2024 13:58:28 -0400 Subject: [PATCH 7/9] Add an example of a higher ranked closure returning boxed future --- .../2024-08-09-async-closures-call-for-testing.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index 0e77b9dbd..9e71e7268 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -34,7 +34,19 @@ where F: FnOnce() -> Pin>>; ``` -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`. +This led to an additional limitation, that it's impossible to express higher-ranked async fn bounds using this without boxing (since a higher-ranked trait bound on `F` cannot lead to a higher-ranked type for `Fut`), leading to unnecessary allocations: + +```rust +fn async_callback(callback: F) +where + F: FnOnce(&str) -> Pin + '_>>; + +async fn do_something(name: &str) {} + +async_callback(|name| Box::pin(async { + do_something(name).await; +})); +``` 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). From 757977a5138ecadec8f4b69d557f4fdd676e7eba Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 14 Aug 2024 14:16:18 -0400 Subject: [PATCH 8/9] Last tweaks --- .../2024-08-09-async-closures-call-for-testing.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index 9e71e7268..e2e5e571b 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -20,21 +20,16 @@ let x = || { async {} }; This had a fundamental limitation that it was impossible to express a closure that returns a future that borrows captured state. -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: +Somewhat relatedly, on the callee side, when users want to take an async closure as an argument, they typically express that as a bound of two different generic types: ```rust fn async_callback(callback: F) where F: FnOnce() -> Fut, Fut: Future; - -// Or: -fn async_callback_boxed(callback: F) -where - F: FnOnce() -> Pin>>; ``` -This led to an additional limitation, that it's impossible to express higher-ranked async fn bounds using this without boxing (since a higher-ranked trait bound on `F` cannot lead to a higher-ranked type for `Fut`), leading to unnecessary allocations: +This also led to an additional limitation that it's impossible to express higher-ranked async fn bounds using this without boxing (since a higher-ranked trait bound on `F` cannot lead to a higher-ranked type for `Fut`), leading to unnecessary allocations: ```rust fn async_callback(callback: F) @@ -146,7 +141,7 @@ We expect to improve async closure signature inference as we move forward. 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 it, since this can usually be handled manually by the caller by using an inner function item, or by using an `Fn` bound instead: for example: +We don't expect to implement this coercion unless there's a particularly good reason to support it, since this can usually be handled manually by the caller by using an inner function item, or by using an `Fn` bound instead, for example: ```rust fn needs_fn_pointer>(callback: fn() -> T) { todo!() } From 4e4c18f05e651b808fbfe45922281bac61e7be41 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 14 Aug 2024 14:16:47 -0400 Subject: [PATCH 9/9] Update posts/inside-rust/2024-08-09-async-closures-call-for-testing.md --- posts/inside-rust/2024-08-09-async-closures-call-for-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md index e2e5e571b..bb65e0e58 100644 --- a/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md +++ b/posts/inside-rust/2024-08-09-async-closures-call-for-testing.md @@ -32,7 +32,7 @@ where This also led to an additional limitation that it's impossible to express higher-ranked async fn bounds using this without boxing (since a higher-ranked trait bound on `F` cannot lead to a higher-ranked type for `Fut`), leading to unnecessary allocations: ```rust -fn async_callback(callback: F) +fn async_callback(callback: F) where F: FnOnce(&str) -> Pin + '_>>;