Ibraheem Ahmed

I wrote a post a while back about how I think Rust's async fn syntax hiding the returned Future type was a mistake. I essentially proposed changing this:

async fn foo(x: &str, y: &str) -> usize { /* ... */ }

To this:

async fn foo(x: &str, y: &str) -> impl Future<Output = usize> + 'in { /* ... */ }

This solves the problem of adding Send/Sync bounds to async traits, marking futures as 'static or Unpin, running synchronous code before the future, boxed future syntax, and other issues that arise from the hidden return type, much like C# does. In my opinion, it makes everything very consistent.

The main pushback against this was that it hurts the general case. However, I wonder how much of that concern goes away if we get something like positional associated types?

async fn foo(x: &str, y: &str) -> impl Future<usize> + 'in { /* ... */ }

Or even a trait alias?

trait Fut<T> = Future<Output = T>;

async fn foo(x: &str, y: &str) -> impl Fut<usize> + 'in { /* ... */ }

Now, obviously, this would be an enormous breaking change and it is probably much too late to make at this point. However, I think there are some ideas we can take from it. I think going down the rabbit hole of extending the current syntax with special cases for everything, as has been discussed with ideas like async(Send), async('static), and box async fn, is problematic. The issue should be addressed at a more general level - we can leave the current syntax as the default, but make the "manual" syntax nice enough that it works for all special cases.

So, it would be really annoying if you had to make such a large change to ensure that the returned future is Unpin:

async fn foo(x: &str, y: &str) -> usize { /* ... */ }

// =>

fn foo<'o, 'a: 'o, 'b: 'o>(x: &'a str, y: &'b str) -> impl Future<Output = usize> + Unpin + 'o {
    async {
        /* ... */

But with a couple language features, not even all specific to async fn, maybe we wouldn't need special syntax?

async fn foo(x: &str, y: &str) -> usize { /* ... */ }

// =>

fn foo(x: &str, y: &str) -> impl Future<usize> + Unpin + 'in = async { /* ... */ }

The above syntax is just hypothetical, but would depend on the 'in lifetime, positional associated types, and one-line function syntax.