Sam Sartor
Git(Hub|Lab)
# Async Functions Considered Harmful? 2020-8-20
Hey! This page is a draft. Don't expect much from it.

If you have any experience with async programming in JavaScript (or in Go, C#, etc), you probably do something like this on a regular basis:

async function makeRequest() { ... }

let task = makeRequest();
// do some other stuff while request is in-flight
let result = await task;

Parallelism is the whole point of asynchronous programming. It allows us to start a task in the background and then do something else while waiting for it to complete. Naturally you should assume the same code works in Rust:

async fn make_request() { ... }

let task = make_request();
// do some other stuff while request is in-flight
let result = task.await;

In fact, this code doesn't work. Unlike every other programming language I am aware of (except maybe Haskell), calling make_request does nothing. The request isn't even started until we get to task.await. The correct version of this is:

async fn make_request() -> Request { ... }

let request = make_request(); // construct a future but do nothing to run it
let task = Task::spawn(request); // actually send request via the global executor
// do some other stuff while request is in-flight, for real this time
let result = task.await; // poll the executor's completion of the task, not the task itself

This is because Rust's async is lazy. And async fn is simply a sugar for:

fn make_request() -> impl Future<Output=Request> {
    async {
        ...
    }
}

This desugar makes the behavior of make_request much more clear: it returns an async block without making any effort to execute the async block. New users might still be surprised that

let _ = async { println!("Hello!") };

never prints hello. But other languages don't have a close equivalent to async { } so any confusion is less likely to persist.

So should we prefer fn -> impl Future over async fn? Is async fn considered harmful? Probably not. The async fn syntax does a lot to eliminate verbosity. It allows users to ignore all sorts of impl Future + Sync + Send + '_ shenanigans, which new users will certainly find valuable. We don't even want to change the desugar to something like

fn make_request() -> impl Future<Output=Request> {
    Task::spawn(async {

    })
}

because it breaks Rust's ability to inline and optimize the common make_request().await case, which users rely on to produce all kinds of zero-cost async abstractions.