| 
 | 1 | +---  | 
 | 2 | +layout: post  | 
 | 3 | +title: "Return type notation MVP: Call for testing!"  | 
 | 4 | +author: Michael Goulet  | 
 | 5 | +team: The Async Working Group <https://www.rust-lang.org/governance/wgs/wg-async>  | 
 | 6 | +---  | 
 | 7 | + | 
 | 8 | +The async working group is excited to announce that [RFC 3654] return type notation (RTN) is ready for testing on nightly Rust. In this post, we'll briefly describe the feature.  | 
 | 9 | + | 
 | 10 | +## The backstory  | 
 | 11 | + | 
 | 12 | +Rust 1.75 [stabilized](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html) async fn in traits (AFIT) and return-position impl Trait in traits (RPITIT). These desugar to anonymous generic associated types (GATs). However, unlike GATs, users of these types cannot use `where` clauses to further restrict these return types. This is known as the ["send bound"](https://smallcultfollowing.com/babysteps/blog/2023/02/01/async-trait-send-bounds-part-1-intro/) problem, since it often affects `Send` bounds on futures in the async ecosystem.  | 
 | 13 | + | 
 | 14 | +### An example  | 
 | 15 | + | 
 | 16 | +Consider a trait `Foo` with a `method` that returns a type of `impl Future<Output = ()>`. We want to write a function that calls `method` and spawns the future on another thread:  | 
 | 17 | + | 
 | 18 | +```rust  | 
 | 19 | +fn spawn<T>(f: impl Future<Output = T> + Send + 'static) {}  | 
 | 20 | + | 
 | 21 | +trait Foo {  | 
 | 22 | +    fn method() -> impl Future<Output = ()>; // <-- RPITIT.  | 
 | 23 | +}  | 
 | 24 | + | 
 | 25 | +fn needs_sendable_future<T: Foo>()  | 
 | 26 | +where  | 
 | 27 | +    // How do we further restrict `T::method()`  | 
 | 28 | +    // to be `Send + 'static`?  | 
 | 29 | +{  | 
 | 30 | +    spawn(T::method());  | 
 | 31 | +    //~^ ERROR: `impl Future<Output = ()>` is not `Send`!  | 
 | 32 | +}  | 
 | 33 | +```  | 
 | 34 | + | 
 | 35 | +Specifically, we may not want to restrict the *declaration* of `Foo`, since changing it in the declaration would restrict *all* implementations of `Foo`.  | 
 | 36 | + | 
 | 37 | +```rust  | 
 | 38 | +trait Foo {  | 
 | 39 | +    fn method() -> impl Future<Output = ()> + Send + 'static;  | 
 | 40 | +    //                                      ~~~~~~~~~~~~~~~~  | 
 | 41 | +    //                                      Not what we want.  | 
 | 42 | +}  | 
 | 43 | +```  | 
 | 44 | + | 
 | 45 | +So, on stable Rust, we have no way of expressing this restriction when using AFIT or RPITIT. In contrast, we can express this today if we were to use a GAT directly:  | 
 | 46 | + | 
 | 47 | +```rust  | 
 | 48 | +trait Foo {  | 
 | 49 | +    type MethodFuture: Future<Output = ()>;  | 
 | 50 | +    fn method() -> Self::MethodFuture;  | 
 | 51 | +}  | 
 | 52 | + | 
 | 53 | +fn needs_sendable_future<T: Foo>()  | 
 | 54 | +where  | 
 | 55 | +    // We can restrict this to only implementors of `Foo`  | 
 | 56 | +    // whose `MethodFuture` is `Send + 'static`, so we can  | 
 | 57 | +    // call `spawn` below:  | 
 | 58 | +    T::MethodFuture: Send + 'static  | 
 | 59 | +{  | 
 | 60 | +    spawn(T::method());  | 
 | 61 | +}  | 
 | 62 | +```  | 
 | 63 | + | 
 | 64 | +However, using GATs means that implementors of `Foo` have to write out the return type explicitly, `type MethodFuture = ...`, which doesn't ([yet](https://github.com/rust-lang/rust/pull/120700)) work if we have an anonymous, unnameable `Future` type!  | 
 | 65 | + | 
 | 66 | +## The solution  | 
 | 67 | + | 
 | 68 | +In [RFC 3654] we introduced return type notation (RTN). This will allow us to write `where` clause bounds that restrict the return types of functions and methods that use async fn in traits (AFIT) and return-position impl Trait in traits (RPITIT). Extending the example above, RTN lets us write:  | 
 | 69 | + | 
 | 70 | +```rust  | 
 | 71 | +fn needs_sendable_future<T: Foo>()  | 
 | 72 | +where  | 
 | 73 | +    T::method(..): Send + 'static // Yay!  | 
 | 74 | +{  | 
 | 75 | +    spawn(T::method());  | 
 | 76 | +    //~^ Works!  | 
 | 77 | +}  | 
 | 78 | +```  | 
 | 79 | + | 
 | 80 | +## Restrictions  | 
 | 81 | + | 
 | 82 | +Currently, RTN is only allowed for trait associated functions and methods with lifetime generics (not const or type generics) that use:  | 
 | 83 | + | 
 | 84 | +* async fn in traits (AFIT) or  | 
 | 85 | +* return-position impl Trait in traits (RPITIT) where the impl Trait is the outermost return type, i.e. `-> impl Trait`, but not `-> Box<impl Trait>`.  | 
 | 86 | + | 
 | 87 | +These restrictions are described in further detail in [RFC 3654].  | 
 | 88 | + | 
 | 89 | +## How do I help?  | 
 | 90 | + | 
 | 91 | +We'd love for you to test out this feature on the latest Rust nightly compiler[^nightly].  | 
 | 92 | + | 
 | 93 | +[^nightly]: Make sure to run `rustup update nightly` (or however you manage your Rust releases), since the feature is very new and is still unstable!  | 
 | 94 | + | 
 | 95 | +Specifically, we'd like for you to identify traits where you're unnecessarily restricting your trait definitions with `+ Send` or similar bounds:  | 
 | 96 | + | 
 | 97 | +```rust  | 
 | 98 | +// Instead of writing a trait like:  | 
 | 99 | + | 
 | 100 | +trait Foo {  | 
 | 101 | +    fn method() -> impl Future<Output = ()> + Send + 'static;  | 
 | 102 | +}  | 
 | 103 | + | 
 | 104 | +// Write this:  | 
 | 105 | + | 
 | 106 | +trait Foo {  | 
 | 107 | +    async fn method();  | 
 | 108 | +}  | 
 | 109 | + | 
 | 110 | +// And then at the call site, add:  | 
 | 111 | + | 
 | 112 | +fn use_foo<T: Foo>()  | 
 | 113 | +where  | 
 | 114 | +    T::method(..): Send + 'static,  | 
 | 115 | +{}  | 
 | 116 | +```  | 
 | 117 | + | 
 | 118 | +Similarly, we'd like for you to identify traits that currently are returning GATs for the same reason:  | 
 | 119 | + | 
 | 120 | +```rust  | 
 | 121 | +// Instead of writing this in the trait and call site:  | 
 | 122 | + | 
 | 123 | +trait Foo {  | 
 | 124 | +    type MethodFuture: Future<Output = ()>;  | 
 | 125 | +    fn method() -> Self::MethodFuture;  | 
 | 126 | +}  | 
 | 127 | + | 
 | 128 | +fn use_foo<T: Foo>()  | 
 | 129 | +where  | 
 | 130 | +    T::MethodFuture: Send + 'static,  | 
 | 131 | +{}  | 
 | 132 | + | 
 | 133 | +// Write this:  | 
 | 134 | + | 
 | 135 | +trait Foo {  | 
 | 136 | +    async fn method();  | 
 | 137 | +}  | 
 | 138 | + | 
 | 139 | +fn use_foo<T: Foo>()  | 
 | 140 | +where  | 
 | 141 | +    T::method(..): Send + 'static,  | 
 | 142 | +{}  | 
 | 143 | +```  | 
 | 144 | + | 
 | 145 | +Note, however, that we don't yet support RTN in type position. So while, with the first version, you can write:  | 
 | 146 | + | 
 | 147 | +```rust  | 
 | 148 | +struct Bar<T: Foo> {  | 
 | 149 | +    field: T::MethodFuture,  | 
 | 150 | +}  | 
 | 151 | +```  | 
 | 152 | + | 
 | 153 | +You can't yet, with the second version, write:  | 
 | 154 | + | 
 | 155 | +```rust  | 
 | 156 | +struct Bar<T: Foo> {  | 
 | 157 | +    field: T::method(..),  | 
 | 158 | +}  | 
 | 159 | +```  | 
 | 160 | + | 
 | 161 | +We'd be interested in hearing about any places where you would run into this limitation.  | 
 | 162 | + | 
 | 163 | +We're excited for RTN to make it easier to use async fn in traits (AFIT) in `Send`-bound-heavy async Rust ecosystems.  | 
 | 164 | + | 
 | 165 | +As always, take a look at the [RFC][RFC 3654] itself for a detailed explanation for why we settled on this design, in particular the [frequently-asked questions and rationale](https://rust-lang.github.io/rfcs/3654-return-type-notation.html#rationale-and-alternatives).  | 
 | 166 | + | 
 | 167 | +[RFC 3654]: https://rust-lang.github.io/rfcs/3654-return-type-notation.html  | 
 | 168 | +[RFC 3425]: https://rust-lang.github.io/rfcs/3425-return-position-impl-trait-in-traits.html  | 
0 commit comments