Killing `Ok(())` without ok-wrapping

async changes the return type of the function, so I think fn foo() -> async T would have been a more logical syntax for Rust.

async fn may seem natural due to other languages normalizing such syntax, but so was await foo().

2 Likes

Not quite, it conflates two separate things: implicit construction of futures using quasi-imperative code (the thing which the async block does) and convenience shortcut for impl Future<Output=T>. So a more logical approach would've been to introduce a separate syntax for the latter:

// explicit return type and future construction
fn foo() -> impl Future<Output = T> { .. }

// explicit return type and implicit future construction
fn foo() -> impl Future<Output = T> { async { .. } }

// shortcut for { async { .. } }
fn foo() -> impl Future<Output = T> async { .. }

// implicit return type and future construction
// @ is a placeholder for a hypothetical shortcut for
// impl Future<Output = T>
fn foo() -> @T async { .. }

// You could add additional bounds, which would be desugared into
// impl Future<Output = T> + Send
fn foo() -> @T + Send async { .. }

// implicit return type and explicit future construction
fn foo() -> @T { .. }

The problem is that the @T async { .. } variant is overwhelmingly more common in practice than the other ones, which makes @ and async redundant in a sense. With async fns feature orthogonality and consistency was somewhat sacrificed in favor of convenience.

2 Likes

async fn also does a transformation on the lifetime generics of the function signature, it's not exactly the same as just changing -> T { ... } to -> impl Future<T> { async move { ... } } when lifetimes are involved.

3 Likes

I think it being a keyword gives it a license to be more than strictly an alias for a Future type.

I'd pretend that keywords after -> are broadly modifiers to function's return type, which can also modify function's body to make the wrapped type created seamlessly. try would fit there as Result-wrapping. Even impl Trait could pretend to be one of these, since it behaves like wrapping the returned type in an anonymous transparent Newtype that delegates only this trait. They're not exact 1:1 equivalents of such types written out by hand, but that's why they're separate keywords.

The OP has asked us to close the thread as it has stayed relatively off topic. If there's something that you all wish to discuss further, please feel free to make a new thread on that focus!