Async/await

Asynchronous programming is an essential technique for writing efficient and responsive code, especially when dealing with I/O-bound tasks or tasks that may take a long time to complete. Rust provides an asynchronous programming model through the async and await keywords1, which allow you to write non-blocking code cleanly. The async and await pattern in Rust is similar to that in other languages like Python and TypeScript.

In Python, you use the async def syntax to define an asynchronous function, and the await keyword to call an asynchronous function:

import asyncio

async def fetch_data():
    # ...

async def main():
    data = await fetch_data()
    print(data)

asyncio.run(main())

In TypeScript, you use the async keyword before the function definition, and the await keyword to call an asynchronous function:

async function fetchData(): Promise<string> {
    // ...
}

async function main() {
    const data = await fetchData();
    console.log(data);
}

main();

Rust's async and await model works similarly to Python and TypeScript.

Async and await

In Rust, the async keyword is used to define asynchronous functions. An asynchronous function is a function that can be paused and resumed later, allowing other tasks to run concurrently2. Asynchronous functions in Rust always return a Future. A Future is a trait that represents a value that may not be available yet but will be at some point in the future.

Here's an example of an asynchronous function in Rust:

#![allow(unused)]
fn main() {
async fn fetch_data() -> Result<String, String> {
    // ...
}
}

If you invoke this function, you get back a Future. But the return type is Result, not Future. That's because Rust hides that detail from us a little bit. Instead of making us put Future everywhere, we have a little friendlier syntax.

Just remember that when you invoke the function, it doesn't execute anything until you await it.

The await keyword is used to pause the execution of an asynchronous function and wait for a Future to resolve. When a Future is awaited, the current task is suspended, allowing other tasks (such as the one you awaited) to run concurrently. Once the awaited Future resolves, the execution of the suspended task resumes.

Here's an example of how to use the await keyword to call an asynchronous function:

async fn main() {
    match fetch_data().await {
        Ok(data) => println!("Data: {}", data),
        Err(error) => println!("Error: {}", error),
    }
}

In this example, the fetch_data asynchronous function is called with the .await syntax. The main function is also defined as asynchronous using the async keyword.

Note that you can only use the await keyword inside an asynchronous function. If you try to use await in a non-async function, you'll get a compile-time error.

Async runtimes

To use async code, you'll need some sort of async runtime! The runtime is responsible for scheduling tasks onto threads. The typical one people go for these days is Tokio, but there are other options. And if you're ever feeling ambitious, you can write your own!


1

await looks like a method or field when you use it, but it's a keyword and is used in syntax as such. It's a keyword the same way that match is, but often feels like you're calling a method, so it can be kind of confusing.

2

Even with a single-threaded async runtime, tasks are concurrent, although they may not run in parallel.