From 9bc6b374c043d77d9acb2e3e9dd9da69a19bcfdb Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Tue, 26 Oct 2021 09:34:01 -0700 Subject: [PATCH 1/8] Add RFC for static async fn in traits --- text/0000-static-async-fn-in-trait.md | 240 ++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 text/0000-static-async-fn-in-trait.md diff --git a/text/0000-static-async-fn-in-trait.md b/text/0000-static-async-fn-in-trait.md new file mode 100644 index 00000000000..40d729a0beb --- /dev/null +++ b/text/0000-static-async-fn-in-trait.md @@ -0,0 +1,240 @@ +# Draft RFC: Static async fn in traits + +- Feature Name: `async_fn_in_traits` +- Start Date: 2021-10-13 +- RFC PR: [rust-lang/rfcs#0000](/~https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](/~https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Support `async fn` in traits that can be called via static dispatch. These will desugar to an anonymous associated type. + +# Motivation +[motivation]: #motivation + +Async/await allows users to write asynchronous code much easier than they could before. However, it doesn't play nice with other core language features that make Rust the great language it is, like traits. + +In this RFC we will begin the process of integrating these two features and smoothing over a wrinkle that async Rust users have been working around since async/await stabilized nearly 3 years ago. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +You can write `async fn` in traits and trait impls. For example: + +```rust +trait Service { + async fn request(&self, key: i32) -> Response; +} + +struct MyService { + db: Database +} + +impl Service for MyService { + async fn request(&self, key: i32) -> Response { + Response { + contents: self.db.query(key).await.to_string() + } + } +} +``` + +This is useful for writing generic async code. + +Currently, if you use an `async fn` in a trait, that trait is not `dyn` safe. If you need to use dynamic dispatch combined with async functions, you can use the [`async-trait`] crate. We expect to extend the language to support this use case in the future. + +Note that if a function in a trait is written as an `async fn`, it must also be written as an `async fn` in your implementation of that trait. With the above trait, you could not write this: + +```rust +impl Service for MyService { + fn request(&self, key: i32) -> impl Future { + async { + ... + } + } +} +``` + +Doing so will give you an "expected async fn" error. If you need to do this for some reason, you can use an associated type in the trait: + +```rust +trait Service { + type RequestFut<'a>: Future + where + Self: 'a; + fn request(&self, key: i32) -> RequestFut; +} + +impl Service for MyService { + type RequestFut<'a> = impl Future + 'a + where + Self: 'a; + fn request<'a>(&'a self, key: i32) -> RequestFut<'a> { + async { ... } + } +} +``` + +Note that in the impl we are setting the value of the associated type to `impl Future`, because async blocks produce unnameable opaque types. The associated type is also generic over a lifetime `'a`, which allows it to capture the `&'a self` reference passed by the caller. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## New syntax + +We introduce the `async fn` sugar into traits and impls. No changes to the grammar are needed because the Rust grammar already support this construction, but async functions result in compilation errors in later phases of the compiler. + +```rust +trait Example { + async fn method(&self); +} + +impl Example for ExampleType { + async fn method(&self); +} +``` + +## Semantic rules + +When an async function is present in a trait or trait impl... + +### The trait is not considered dyn safe + +This limitation is expected to be lifted in future RFCs. + +### Both the trait and its impls must use `async` syntax + +It is not legal to use an async function in a trait and a "desugared" function in an impl. + +## Equivalent desugaring + +### Trait + +Async functions in a trait desugar to an associated function that returns a generic associated type (GAT): + +* Just as with [ordinary async functions](https://rust-lang.github.io/rfcs/2394-async_await.html#lifetime-capture-in-the-anonymous-future), the GAT has a generic parameter for every generic parameter that appears on the fn, along with implicit lifetime parameters. +* The GAT has the complete set of where clauses that appear on the `fn`, including any implied bounds. +* The GAT is "anonymous", meaning that its name is an internal symbol that cannot be referred to directly. (In the examples, we will use `$` to represent this name.) + + +```rust +trait Example { + async fn method(&self) + where + WC0..WCn; +} + +// Becomes: + +trait Example { + type $<'me, P0..Pn>: Future + where + WC0..WCn, // Explicit where clauses + Self: 'me; // Implied bound from `&self` parameter + + fn method(&self) -> Self::$<'_> + where + WC0..WCn; +} +``` + +`async fn` that appear in impls are desugared in the same general way as an [existing async function](https://doc.rust-lang.org/reference/items/functions.html#async-functions), but with some slight differences: + +* The value of the associated type `$` is equal to an `impl Future` type, rather than the `impl Future` being the return type of the function +* The function returns a reference to `Self::$<...>` with all the appropriate generic parameters + +Otherwise, the desugaring is the same. The body of the function becomes an `async move { ... }` block that both (a) captures all parameters and (b) contains the body expression. + +```rust +impl Example for ExampleType { + async fn method(&self) { + ... + } +} + +impl Example for ExampleType { + type $<'me, P0..Pn> = impl Future + 'me + where + WC0..WCn, // Explicit where clauses + Self: 'me; // Implied bound from `&self` parameter + + fn method(&self) -> Self::$<'_, P0..Pn> { + async move { ... } + } +} +``` + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Why are we adding this RFC now? + +This RFC represents the least controversial addition to async/await that we could add right now. It was not added before due to limitations in the compiler that have now been lifted – namely, support for [Generic Associated Types][gat] and [Type Alias Impl Trait][tait]. + +[gat]: /~https://github.com/rust-lang/generic-associated-types-initiative +[tait]: /~https://github.com/rust-lang/rust/issues/63063 + +## Why are the result traits not dyn safe? + +Supporting async fn and dyn is a complex topic -- you can read the details on the [dyn traits](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html) page of the async fundamentals evaluation doc. + +## Can we add support for dyn later? + +Yes, nothing in this RFC precludes us from making traits containing async functions dyn safe, presuming that we can overcome the obstacles inherent in the design space. + +## What are users using today and why don't we just do that? + +Users in the ecosystem have worked around the lack of support for this feature with the [async-trait] proc macro, which desugars into `Box`s instead of anonymous associated types. This has the disadvantage of requiring users to use `Box` along with all the [performance implications] of that, which prevent some use cases. It is also not suitable for users like [embassy](/~https://github.com/embassy-rs/embassy), which aim to support the "no-std" ecosystem. + +[async-trait]: /~https://github.com/dtolnay/async-trait +[performance implications]: https://rust-lang.github.io/wg-async-foundations/vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.html + +## Will anyone use async-trait crate once this RFC lands? + +The async-trait crate will continue to be useful after this RFC, because it allows traits to remain `dyn`-safe. This is a limitation in the current design that we plan to address in the future. + +# Prior art +[prior-art]: #prior-art + +## The `async-trait` crate + +The most common way to use `async fn` in traits is to use the [`async-trait`] crate. This crate takes a different approach to the one described in this RFC. Async functions are converted into ordinary trait functions that return `Box` rather than using an associated type. This means that the resulting traits are dyn safe and avoids a dependency on generic associated types, but it also has two downsides: + +* Requires a box allocation on every trait function call; while this is often no big deal, it can be prohibitive for some applications. +* Requires the trait to state up front whether the resulting futures are `Send` or not. The [`async-trait`] crate defaults to `Send` and users write `#[async_trait(?Send)]` to disable this default. + +Since the async function support in this RFC means that traits are not dyn safe, we do not expect it to completely displace uses of the `#[async_trait]` crate. + +[`async-trait`]: https://crates.io/crates/async-trait + +## The real-async-trait crate + +The [`real-async-trait`] lowers `async fn` to use GATs and impl Trait, roughly as described in this RFC. + +[`real-async-trait`]: https://crates.io/crates/real-async-trait + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Dyn compatibility + +It is not a breaking change for traits to become dyn safe. We expect to make traits with async functions dyn safe, but doing so requires overcoming a number of interesting challenges, as described in the [async fundamentals evaluation doc][eval]. + +## Impl trait in traits + +The [impl trait initiative] is expecting to propose "impl trait in traits" (see the [explainer](https://rust-lang.github.io/impl-trait-initiative/explainer/rpit_trait.html) for a brief summary). This RFC is compatible with the proposed design. + +## Ability to name the type of the returned future + +This RFC does not propose any means to name the future that results from an `async fn`. That is expected to be covered in a future RFC from the [impl trait initiative]; you can read more about the [proposed design](https://rust-lang.github.io/impl-trait-initiative/explainer/rpit_names.html) in the explainer. + +[eval]: https://rust-lang.github.io/async-fundamentals-initiative/evaluation.html +[impl trait initiative]: https://rust-lang.github.io/impl-trait-initiative/ From 5c7db81f55b68db4181894bacba4b59caab14739 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Tue, 26 Oct 2021 09:50:48 -0700 Subject: [PATCH 2/8] Update RFC number --- ...ic-async-fn-in-trait.md => 3185-static-async-fn-in-trait.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-static-async-fn-in-trait.md => 3185-static-async-fn-in-trait.md} (99%) diff --git a/text/0000-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md similarity index 99% rename from text/0000-static-async-fn-in-trait.md rename to text/3185-static-async-fn-in-trait.md index 40d729a0beb..09c331611a5 100644 --- a/text/0000-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -2,7 +2,7 @@ - Feature Name: `async_fn_in_traits` - Start Date: 2021-10-13 -- RFC PR: [rust-lang/rfcs#0000](/~https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3185](/~https://github.com/rust-lang/rfcs/pull/3185) - Rust Issue: [rust-lang/rust#0000](/~https://github.com/rust-lang/rust/issues/0000) # Summary From 0a1ddd3930afdaf6642a22724b7273f75d3e2359 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Wed, 27 Oct 2021 10:30:43 -0700 Subject: [PATCH 3/8] Add missing type parameters --- text/3185-static-async-fn-in-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index 09c331611a5..616028d2a1c 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -133,7 +133,7 @@ trait Example { WC0..WCn, // Explicit where clauses Self: 'me; // Implied bound from `&self` parameter - fn method(&self) -> Self::$<'_> + fn method(&self) -> Self::$<'_, P0..Pn> where WC0..WCn; } From a610469cb39e412fcd2517ba692a452891aaf4e2 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Thu, 4 Nov 2021 16:46:09 -0700 Subject: [PATCH 4/8] Add section on allowing desugared form --- text/3185-static-async-fn-in-trait.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index 616028d2a1c..5f8948d862e 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -232,6 +232,24 @@ It is not a breaking change for traits to become dyn safe. We expect to make tra The [impl trait initiative] is expecting to propose "impl trait in traits" (see the [explainer](https://rust-lang.github.io/impl-trait-initiative/explainer/rpit_trait.html) for a brief summary). This RFC is compatible with the proposed design. +## Allowing sugared and desugared forms + +In the current proposal, `async fn`s in traits must be implemented using `async fn`. Using a desugared form is not allowed, which can preclude implementations from doing things like doing some work at call time before returning a future. It would also be backwards-incompatible for library authors to move between the sugared and desugared form. + +Once impl trait in traits is supported, we can redefine the desugaring of `async fn` in traits in terms of that feature (similar to how `async fn` is desugared for free functions). That provides a clear path to allowing the desugared form to be used interchangeably with the `async fn` form. In other words, you should be able to write the following: + +```rust +trait Example { + async fn method(&self); +} + +impl Example for ExampleType { + fn method(&self) -> impl Future + '_ {} +} +``` + +It could also be made backward-compatible for the trait to change between the sugared and desugared form. + ## Ability to name the type of the returned future This RFC does not propose any means to name the future that results from an `async fn`. That is expected to be covered in a future RFC from the [impl trait initiative]; you can read more about the [proposed design](https://rust-lang.github.io/impl-trait-initiative/explainer/rpit_names.html) in the explainer. From ea25d08804492bf8f3df6886ce75ff3a6d8e1e77 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Sat, 6 Nov 2021 16:41:39 -0700 Subject: [PATCH 5/8] Add more missing generics --- text/3185-static-async-fn-in-trait.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index 5f8948d862e..73f9d8828d4 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -133,7 +133,7 @@ trait Example { WC0..WCn, // Explicit where clauses Self: 'me; // Implied bound from `&self` parameter - fn method(&self) -> Self::$<'_, P0..Pn> + fn method(&self) -> Self::$<'_, P0..Pn> where WC0..WCn; } @@ -148,7 +148,7 @@ Otherwise, the desugaring is the same. The body of the function becomes an `asyn ```rust impl Example for ExampleType { - async fn method(&self) { + async fn method(&self) { ... } } @@ -159,7 +159,7 @@ impl Example for ExampleType { WC0..WCn, // Explicit where clauses Self: 'me; // Implied bound from `&self` parameter - fn method(&self) -> Self::$<'_, P0..Pn> { + fn method(&self) -> Self::$<'_, P0..Pn> { async move { ... } } } From e54f7ed2e1ab517f47604c5e5acf2ab56ca5d8f6 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Tue, 9 Nov 2021 17:17:49 -0800 Subject: [PATCH 6/8] Fix wording about return type --- text/3185-static-async-fn-in-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index 73f9d8828d4..cd80c3108e5 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -142,7 +142,7 @@ trait Example { `async fn` that appear in impls are desugared in the same general way as an [existing async function](https://doc.rust-lang.org/reference/items/functions.html#async-functions), but with some slight differences: * The value of the associated type `$` is equal to an `impl Future` type, rather than the `impl Future` being the return type of the function -* The function returns a reference to `Self::$<...>` with all the appropriate generic parameters +* The function returns `Self::$<...>` with all the appropriate generic parameters Otherwise, the desugaring is the same. The body of the function becomes an `async move { ... }` block that both (a) captures all parameters and (b) contains the body expression. From 6cf7d28f3026083277e2192fbd2481daa8ee2a1b Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Fri, 19 Nov 2021 17:13:21 -0800 Subject: [PATCH 7/8] Remove draft designation --- text/3185-static-async-fn-in-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index cd80c3108e5..b9c928d7314 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -1,4 +1,4 @@ -# Draft RFC: Static async fn in traits +# Static async fn in traits - Feature Name: `async_fn_in_traits` - Start Date: 2021-10-13 From 72b7875cd2b6448a92e1e2925e8ee6258b6eca72 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 6 Dec 2021 18:34:33 -0500 Subject: [PATCH 8/8] link to rust issue --- text/3185-static-async-fn-in-trait.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/text/3185-static-async-fn-in-trait.md b/text/3185-static-async-fn-in-trait.md index b9c928d7314..71039f2a795 100644 --- a/text/3185-static-async-fn-in-trait.md +++ b/text/3185-static-async-fn-in-trait.md @@ -3,7 +3,7 @@ - Feature Name: `async_fn_in_traits` - Start Date: 2021-10-13 - RFC PR: [rust-lang/rfcs#3185](/~https://github.com/rust-lang/rfcs/pull/3185) -- Rust Issue: [rust-lang/rust#0000](/~https://github.com/rust-lang/rust/issues/0000) +- Rust Issue: [rust-lang/rust#91611](/~https://github.com/rust-lang/rust/issues/91611) # Summary [summary]: #summary @@ -217,9 +217,7 @@ The [`real-async-trait`] lowers `async fn` to use GATs and impl Trait, roughly a # Unresolved questions [unresolved-questions]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +- None. # Future possibilities [future-possibilities]: #future-possibilities