Telerik blogs
Industry NewsT2 Light_1200x303

Asynchronous programming greatly improves our development with faster and more responsive code. Learn about how async works in Rust and when you should use it.

Asynchronous programming is a form of parallel programming that allows a unit of work to run separately from the primary application thread and then proceeds to notify the main thread if said work was executed successfully or not. The benefits of asynchronous code cannot be overemphasized. From better performance to enhanced responsiveness, asynchronous programming has in many ways improved the way we write code and consequently its quality.

Why Async?

We all love how Rust allows us to write fast, safe software, so it’s easy to see why one might ask the point of bothering with async programming in Rust. A simple answer is: it allows us to run multiple tasks concurrently on the same OS thread. For example, a typical way to download two different webpages in a threaded application would be to spread the work across two different threads like this:

#![allow(unused_variables)]
fn main() {
  fn get_two_sites() {
     // Spawn two threads to do work.
      let thread_one = thread::spawn(|| download("https://www.jellof.com"));
      let thread_two = thread::spawn(|| download("https://www.yam.com"));
  
      // Wait for both threads to complete.
      thread_one.join().expect("thread one panicked");
      thread_two.join().expect("thread two panicked");
  }
}

This approach will work very well for most applications, especially as that is what threads were designed to do (run multiple, different tasks at once). However, they inherently come with some limitations—the most obvious being the overhead that arises as a result of switching and sharing data between different threads as a thread which sits idly doing nothing uses up valuable system resources.

The function in the sample above can easily be rewritten with Rust’s async/.await notation, which will allow us to run multiple tasks at once without creating multiple threads:

#![allow(unused_variables)]
fn main() {
  async fn get_two_sites_async() {
      // Create two different "futures" which, when run to completion,
      // will asynchronously download the webpages.
      let future_one = download_async("https://www.foo.com");
      let future_two = download_async("https://www.bar.com");
  
      // Run both futures to completion at the same time.
      join!(future_one, future_two);
  }
}

async/.await in Rust

async/.await is Rust's beautifully syntaxed built-in tool for writing async functions. It is structured in such a way that it looks like synchronous code. The async block changes a block of code into a state machine, which implements a trait called Future. So while calling a blocking function in a synchronous method blocks the entire thread, Futures yield control of the thread, allowing other Futures to execute.

To create an async function, you use the async fn syntax:

async fn do_amazing_stuff() { ... }

The value returned by async fn is called a Future. For anything to happen thereafter, the Future needs to be run on an executor

// `block_on` blocks the current thread until the provided future has run to
// completion. Other executors provide more complex behavior, like scheduling
// multiple futures onto the same thread.
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
}

The .await is used inside the async fn to wait for the completion of another type that implements the Future trait (e.g. the output of another async function). It doesn’t block the current thread, but instead asynchronously waits for the Future to complete, allowing other tasks to run if the Future is currently unable to make progress.

The State of Asynchronous Rust

It can be pretty hard to know the right tools to use, what libraries to invest in, or what documentation to read as the ecosystem has evolved over time. However, the Future trait inside the standard library and the async/await language feature has recently been stabilized. The ecosystem as a whole is therefore in the midst of migrating to the newly stabilized API, after which point churn will be significantly reduced.

It is worthy to note that there are still rapid developments being made, and the Rust Asynchronous experience is somewhat unpolished. The async/await language feature is still new. Important extensions like async fn syntax in trait methods are still unimplemented, and the current compiler error messages can be difficult to parse.

Regardless, Rust is well on its way to having some of the most performant and ergonomic support for asynchronous programming around, if you’re not afraid of doing some exploration.

Conclusion

Overall, asynchronous applications have the potential to be much faster and use fewer resources than a corresponding threaded implementation. However, there is a cost. Threads are natively supported by the operating system, and using them doesn’t require any special programming model—any function can create a thread, and calling a function that uses threads is usually just as easy as calling any normal function. Asynchronous functions on the other hand require special support from the language or libraries.

Traditional threaded applications can be quite effective, given Rust’s small memory footprint and predictability, which means that you can get far without ever using async.

The increasing complexity of the asynchronous programming model isn’t always worth it, and it’s important to consider your use case and considering If your application would be better served by using a simpler threaded model.

Enjoy your dive into the world of asynchronous programming in Rust!


Obinna Ekwuno (002)
About the Author

Obinna Ekwuno

Writer of all things technical and inspirational, developer and community advocate. In a love-love relationship with JavaScript, a web accessibility nerd. 


Comments

Comments are disabled in preview mode.