A Rust Async Primer-Pt. 1
A short stroll through asynchronous concepts in Rust.
These days, if you’re writing code in a reasonably modern language, it’s difficult to escape the idea of asynchronous programming. If you search for the most popular programming languages of 2022, every single entry in the list supports asynchronous programming. Most of them even use the same keywords in their syntax, async, and await. This article will be the first in a series of articles that will present the general concepts, followed by a brief discussion of how async is implemented in Rust, and finally some sample code to demonstrate the concepts.
Intro to Asynchronicity
Let’s start by clearing up some common misunderstandings. First Concurrency is the act of performing multiple tasks simultaneously. For example, I can both walk and chew bubble gum at the same time (most days). A different example would be making a pot of coffee while I read the morning paper. Both of these are examples of concurrency but the means by which are achieved differ. Walking and chewing gum at the same time is an example of parallelism since I can do both things simultaneously. Making a pot of coffee while reading the paper is an example of synchronicity since I can’t physically do both tasks simultaneously. Instead, I prep the coffee maker and start it, then begin reading my paper. When the pot is done, I then stop reading, pour a cup, and go back to reading. I can work on both tasks at once, just not at the same time. In other words, I move on to another task, while waiting for the previous one to complete.
In languages that support async programming, in very simplified terms, async code is implemented using a compiler-generated state machine. This state machine uses the
await keyword to break up the execution units into smaller chunks, creating a code block from the beginning of the method to the first
await boundary as one task. The next execution chunk would be all the code between the first
await, and the second
await, if there is one. I mention this detail to point out that threads do not enter into the picture. They can, and certainly do, but the point is that asynchronous code has nothing to do with threads.
Why did anyone want to create this complicated-sounding mess? In short, it beats the alternatives. This technique allows us to create a large number of independent concurrent tasks without the expensive requirement of spinning up a large number of new threads, it’s easier to understand and reason about than the alternatives, and it’s performant.
How Rust Implements Async
When the designers of Rust implemented async/await they had some specific goals in mind, and as a result, there are several differences between Rust and most other languages.
In broad strokes, Rust async is built on two types;
Future represents a piece of code that will execute and return a concrete result at some point in the future.
Task is an in-process
Future that is being driven to completion. In other words, a
Task is a
Future that can schedule its execution.
Async in Rust does not use heap allocations or dynamic dispatching, which makes it extremely performant and keeps the footprint small, allowing async to be used in embedded environments or other resource-constrained apps.
Rust has no built-in runtime for implementing async. It relies on separate crates to provide a runtime. As a consequence, you can select a runtime that aligns most appropriately with your needs. For example, there are both multi-threaded, and single-threaded runtimes. It also means that if you’re not using async, your code won’t be bloated by unused features.
While Rust gives you the components you need to create a custom runtime, the reality is that very few people have the knowledge, time, or desire to put forth that level of effort. Instead, they’ll select an existing runtime. We’ll cover the primary runtimes later. In the next article, we’ll look at examples of async in Rust starting with a basic functioning async example, and adding complexity from there.
This has been part 1 of a 3 part series. If you enjoyed this article, I would appreciate it if you would give it a clap, and be sure to follow me in order to keep up with the rest of the articles in the series where we move beyond introducing the concepts, and into actual code!
Part 2 can of the series can be found here.