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!
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.
Even with a single-threaded async runtime, tasks are concurrent, although they may not run in parallel.