A Quick Rundown of Async/Await in Rust

Reading Time: 4 minutes

As you all know the asyncawait syntax hits stable Rust, as part of the 1.39.0 release. So let’s leverage this stabilized feature to make our code-bases Asynchronous with Zero-cost futures.

In this article, we’ll mainly look into the basics of Async/Await feature:

  • Async/Await: Quick Intro
  • Zero-cost futures
  • Quick Demonstration of Asynchronous Programming

Before diving into our road-map let’s understand the behavior of the Asynchronous programming.
In a nutshell, Asynchronous programming lets us run multiple tasks at the same time on a single OS thread. Multiple scenarios can be handled at the same time because when they are waiting for a response, then they are just idle, so we can let the other tasks keep working on their computations that aren’t waiting.

Now let quickly have a look in the key-points of this article:

Async/Await: Quick Intro

Async/Await built-in tool of Rust Programming for writing asynchronous functions and they are special pieces of Rust syntax that make it possible to yield control of the current thread rather than blocking, then allowing other code to make progress while waiting on an operation to complete.

Now let’s talk about both the features separately:

Async:

Async transforms a block of code into a state machine that implements a trait called Future.
There are two ways to use async are:

  • async fn
  • async block
async fn add(a: u32, b: u32) -> u32 {
    a + b
}

fn main() {
    let result: impl Furute<Output: u32> = add(2, 3);
    async {
        // some code      
    };
}

This is how you can use the async in your program to make it asynchronous.
Both the async fn & async block will return the value which implements a Future trait.

Await:

Await is a mechanism to run a Future. It asynchronously waits for the future to complete.
So what it does like when a Future is not ready then it yields the control of the current and allows other tasks to run and it doesn’t block the current thread.
We can only use the .await built-in tool inside the async fn or async block.
Like:

async fn add(a: u32, b: u32) -> u32 {
    a + b
}

fn main() {
    let result: impl Furute<Output=u32> = add(2, 3); // I've added the return type just for showing you the actual return type
    async {
        let data: u32 = result.await; // here it asynchronously waits for the Future to complete.
        // rest of the code
    };
}

This is how you can use the .await feature in your program. In the above example, you can see that we are using .await with the result variable because it implements Future trait.

We’ve covered the quick intro part of the async/await, now let’s understand the concept of the Zero-cost futures in Rust Programming:

Zero-cost futures:

Rust’s futures are purely based on the ‘poll’ model which makes them zero-cost.
As in other languages, invoking an async function immediately creates a future and schedules it for execution and awaiting the future isn’t necessary for it to execute. But in Rust, invoking an async function does not do any scheduling, and here the main thing you’ll notice is that futures feel “lazy”: they won’t do anything until you await them inside the async block. After awaiting the Future then the Future executor comes into the picture.
The Future executor takes the Future and run them to completion by calling the poll whenever the Future can make progress. To know more about what happens with the Future under the hood please refer to this.

Now let’s jump into our last but not least step which is to make an Asynchronous program using the Async/Await feature of Rust Programming.

Quick Demonstration of Asynchronous Programming

Let’s compile our whole topic into a piece of code :

use futures::executor::block_on;
use async_std::task;
use std::time::Duration;


async fn func_1() {
    for i in 1..10 {
        print!("f1 ");
        if i == 5 {
            task::sleep(Duration::from_secs(2)).await;
        }
    }
}

async fn func_2() {
    for i in 1..10 {
        print!("f2 ");
    }
}

async fn compute() {
    let first = func_1();
    let second = func_2();

    futures::join!(first, second);
}

fn main() {
    block_on(compute());
}

This code shows the concurrent behavior of the functions running on the same OS thread.
Here we have functions func_1 & func_2 and both the running on the same thread but to check the asynchronous behavior of both the functions, we have used the async_std::task::sleep to halt the processing of one task which is intended to print the word “f1” and then will see the control passes to the next task which is intended to print the word “f2” or not because we have used .await with the sleep method inside the func_1.
Let’s run this program…

Output:

Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/async-programming`
f1 f1 f1 f1 f1 f2 f2 f2 f2 f2 f2 f2 f2 f2 f1 f1 f1 f1 

This the output we got from our asynchronous code and as you can see when we stop processing our func_1 the control yields from task one and forwarded to another task which prints the “f2”.
Here is the link of GitHub Gist.

This is how we can implement the concept of asynchronous programming in Rust using the feature async/await.
Hope you get acquainted with this feature.

Thanks for reading!!!


Knoldus-blog-footer-image

Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than a year in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute towards the community which he attained from the community.