Skip to main content
Rust Programming
CHAPTER 23 Beginner

Async Programming in Rust

Updated: May 18, 2026
5 min read

# CHAPTER 23

Async Programming in Rust

1. Chapter Introduction

In Chapter 22, we learned how to spawn OS threads. However, OS threads are "heavy" (taking ~2MB of RAM each). If you build a web server and spawn an OS thread for every user, handling 10,000 users will crash your server. To achieve massive concurrency for network operations, modern Rust relies on Asynchronous Programming. In this chapter, we will learn about async/await, Futures, and how to use the Tokio runtime.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand the difference between OS Threads and Async tasks.
  • Define asynchronous functions using the async keyword.
  • Pause execution using the .await keyword.
  • Understand the concept of a Future.
  • Install and configure the Tokio async runtime.

3. What is Async? (The Kitchen Analogy)

Synchronous (Single Thread): You put a cake in the oven. You stand there staring at the oven for 30 minutes doing nothing else until it finishes. OS Threads: You hire a second chef (Thread 2) just to stare at the oven while you chop vegetables. (Expensive to hire). Asynchronous: You put the cake in the oven, set a timer, and immediately start chopping vegetables. When the timer rings, you pause chopping and check the oven. (One chef, maximum efficiency).

Async is designed for I/O bound tasks (waiting for network requests, database queries, or file reads).

4. async and await

When you mark a function as async, it no longer executes immediately. Instead, it returns a Future (a promise that it will yield a value eventually).
rust
1234567891011
// This function doesn't return data immediately. It returns a Future.
async fn fetch_data() -> String {
    String::from("Data fetched from database!")
}

async fn run_program() {
    // .await pauses run_program() until fetch_data() completes.
    // While it is paused, the CPU is free to do other tasks!
    let data = fetch_data().await;
    println!("{}", data);
}

5. The Need for a Runtime (Tokio)

Here is a quirky fact about Rust: The Rust Standard Library does NOT include an Async Runtime. If you write async fn main(), the compiler will throw an error. You must bring in a third-party runtime to manage the execution of Futures. The industry standard is Tokio.

1. Install Tokio: Add this to your Cargo.toml:

toml
12
[dependencies]
tokio = { version = "1", features = ["full"] }

2. Setup Tokio in main.rs: By adding the #[tokio::main] macro, Tokio sets up the background engine and allows main to be async!

rust
12345678910111213
use tokio::time::{sleep, Duration};

// The macro wraps main in the Tokio runtime
#[tokio::main]
async fn main() {
    println!("Starting task...");

    // Instead of thread::sleep (which blocks the whole thread), 
    // we use tokio::time::sleep (which yields control to the runtime!)
    sleep(Duration::from_secs(2)).await;

    println!("Task finished after 2 seconds!");
}

6. Spawning Async Tasks

Just like thread::spawn, Tokio allows you to spawn thousands of lightweight tasks (green threads) that all run concurrently on a small pool of actual OS threads.
rust
1234567891011121314
#[tokio::main]
async fn main() {
    // Spawn a lightweight Tokio task
    let handle = tokio::spawn(async {
        println!("Running in a background task!");
        "Result Data"
    });

    println!("Running in main task!");

    // Wait for the background task to finish and get the result
    let result = handle.await.unwrap();
    println!("Background returned: {}", result);
}

7. Common Mistakes

  • Using std::thread::sleep in Async Code: If you use the standard thread sleep inside an async function, you freeze the entire OS thread, completely destroying the benefit of Async. Always use tokio::time::sleep and .await it.
  • Forgetting .await: If you call an async fn and forget to .await it, nothing happens! The Future is created but never executed. The compiler will warn you: future is not .await'ed.

8. Best Practices

  • CPU vs I/O: Use Async/Tokio for I/O bound work (APIs, Databases, Files). If you have intense CPU-bound work (like rendering a 3D image or calculating primes), use standard std::thread OS threads.

9. Exercises

  1. 1. Setup a new cargo project and install tokio.
  1. 2. Create an async fn getuserid() -> u32 that returns 101.
  1. 3. In #[tokio::main], call the function, .await the result, and print it.

10. MCQs with Answers

Question 1

What is the primary advantage of Async programming over OS Threads?

Question 2

When you declare an async fn, what does it actually return when called?

Question 3

What keyword is used to pause the current function and wait for a Future to complete, yielding control to the runtime?

Q4. Does the Rust standard library include a built-in async runtime to execute Futures? a) Yes b) No, you must use a third-party crate like Tokio Answer: b) No, you must use a third-party crate.
Question 5

What is the most popular, industry-standard Async Runtime for Rust?

Question 6

What macro must you place above fn main() to allow it to run async code using Tokio?

Question 7

What happens if you call asyncfunc(); but forget to put .await at the end?

Question 8

What is the Tokio equivalent of thread::spawn for creating lightweight background tasks?

Question 9

Why is it terrible to use std::thread::sleep inside an async function?

Q10. Async programming in Rust is best suited for what kind of tasks? a) CPU-bound tasks (Heavy math, video rendering) b) I/O-bound tasks (Database queries, HTTP requests, File reading) Answer: b) I/O-bound tasks.

11. Interview Questions

  • Q: Explain the difference between an OS Thread and a Tokio async task (Green Thread).
  • Q: Why did the Rust language designers choose not to include an Async Runtime in the standard library? (Answer: To keep the standard library incredibly small and suitable for Embedded systems, allowing the community to build tailored runtimes like Tokio).

12. Summary

Async/Await enables Rust to compete with Node.js and Go in the high-concurrency web server space, but with much lower memory usage and no Garbage Collector pauses. By understanding Futures, .await, and the Tokio runtime, you are now equipped to build modern, cloud-native backend infrastructure.

13. Next Chapter Recommendation

Now that we have all the foundational skills, let's build something practical. In Chapter 24: Building Command Line Applications, we will learn how to parse terminal arguments using the clap crate and build professional CLI utilities.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·