DEV Community

Cover image for Day 30: โšก๏ธ Rustic Asynchrony: Decoding the Enigma of Futures and Async/Await! ๐Ÿš€
Aniket Botre
Aniket Botre

Posted on

Day 30: โšก๏ธ Rustic Asynchrony: Decoding the Enigma of Futures and Async/Await! ๐Ÿš€

Welcome, fellow Rust enthusiasts! Today, we embark on a journey through the intricate world of asynchronous programming in Rust. Buckle up as we explore the nuances of Futures, dive into the magic of Async/Await, and unveil the role of the Tokio runtime. Let's unravel the enigma of Rustic Asynchrony!


The Dawn of Asynchronous Programming ๐ŸŒ…๐Ÿ•น๏ธ

In the olden days of synchronous programming, our code ran in sequence, like a well-behaved queue at the post office. But as our programs grew more complex and our computers more powerful, we started craving something moreโ€ฆ asynchronous. Asynchronous programming is a critical aspect of modern software development, particularly in Rust, a language that emphasizes safety and performance. Rust's approach to asynchronous programming is built around the concepts of futures and the async/await syntax, which allow developers to write non-blocking code that can run efficiently and concurrently.


Futures in Rust: Promises of Tomorrow, Today! ๐Ÿฆ€๐Ÿ”ฎ

In Rust, a Future is a value that might not have been computed yet. It's like a promise to do something in the future. If this sounds familiar, it's because JavaScript has a similar concept called Promises. But while Promises are like your mom promising to bake cookies (you never know when they'll be ready), Rust Futures are more like a timer on an oven. They need to be polled to check if they're ready. ๐Ÿชโฒ๏ธ

A Rust future is a value that has the std::future::Future trait from the standard library. It is not like some other languages where a future is a background computation. Rust futures are like state machines that need to be polled to change their state. Wakers help with the polling by telling the task that a resource is ready to go on.

In Rust, the executor takes care of finishing the future by calling Future::poll many times.

Here's a simple Future in action:

use futures::executor::block_on;

async fn hello_world() {
    println!("hello, world!");
}

fn main() {
    let future = hello_world(); // Nothing is printed
    block_on(future); // `future` is run and "hello, world!" is printed
}
Enter fullscreen mode Exit fullscreen mode

In this code, hello_world() is an async function that returns a Future. When we call hello_world(), nothing is printed. It's only when we call block_on(future) that "hello, world!" is printed. This is because block_on polls the Future until it's ready.


Async/Await: The Heroic Duo of Rustic Asynchrony ๐Ÿ’ช

The async/await syntax in Rust, introduced in version 1.39.0, is a game-changer for writing asynchronous code. It's like the conductor of an orchestra, ensuring all parts play together in harmony. The async keyword transforms a block of code into a state machine that can pause and resume execution, while await pauses the execution of the function until the Future is ready. ๐ŸŽถ๐ŸŽต

The async/await syntax in Rust provides a more readable and familiar way to write asynchronous code. When a function is decorated with async, its return type is transformed into a Future. Async functions in Rust are zero-cost, meaning you only pay for what you use, and they can return values just like regular functions.

Here's a simple example:

async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }

async fn learn_and_sing() {
    // Wait until the song has been learned before singing it.
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` is like `.await` but can wait for multiple futures concurrently.
    join!(f1, f2);
}

fn main() {
    async_main().await;
}
Enter fullscreen mode Exit fullscreen mode

In this example, async_main can learn and sing a song while also dancing! This is the power of async/await. ๐ŸŽค๐Ÿ’ƒ


Tokio: The Runtime of Your Dreams โ˜๏ธ๐ŸŒˆ

As we mentioned earlier, async programming in Rust needs a runtime to manage these Futures. However, Rust has no default runtime. It's like a playground without a supervisor, a party without a host. Enter Tokio. Tokio is an event-driven, non-blocking I/O platform for writing asynchronous applications in Rust.

Tokio provides a way for your Futures to be scheduled and run. It's like the stage manager in a play, deciding when and where the actors (Futures) perform.

It provides a multi-threaded runtime for executing asynchronous code, an asynchronous version of the standard library, and a large ecosystem of libraries.

Here's a simple example of a Tokio runtime:

#[tokio::main]
async fn main() {
    let task = tokio::spawn(async {
        // Some async computation here...
    });

    task.await.unwrap();
}
Enter fullscreen mode Exit fullscreen mode

In this example, #[tokio::main] sets up a Tokio runtime and starts running the main function on it. tokio::spawn is used to spawn a new asynchronous task onto the Tokio runtime.


Comparison with JavaScript's Promises and Async/Await

Rust's futures and async/await can be compared to JavaScript's Promises and async/await. In JavaScript, Promises are eager and include a runtime and event loop that handles scheduling async tasks and running callbacks. This makes working with async code in JavaScript more transparent for developers.

In contrast, Rust is a low-level language that does not include a runtime for scheduling async tasks by default. Instead, Rust provides traits, utility types, and language features that allow library authors to build async runtimes and for application developers to work with async values.


Use Cases for Futures and Async/Await in Rust

Asynchronous programming is crucial in web development. It allows handling multiple requests concurrently, thereby improving the performance of web applications. With Rust's powerful async/await syntax and the Tokio runtime, you're well-equipped to tackle the challenges of web development.

But the use of Futures, async/await, and Tokio isn't limited to web development. They can be used anywhere you need to handle multiple tasks concurrently, such as in-game development, data processing, and more! ๐Ÿ‘พ๐ŸŽฎ


Conclusion: Unveiling the Rustic Enigma ๐ŸŽญ

And there you have it, the unravelling of Rustic Asynchrony! We've navigated through Futures, embraced the elegance of Async/Await, met our guardian Tokio, and glimpsed into the future of Rust programming. As you embark on your Rust journey, may your async adventures be as smooth as a Tokio-powered ride through the skies of Rustic possibilities. Happy coding! ๐ŸŒโœจ

Top comments (0)