DEV Community

Ben Santora
Ben Santora

Posted on

Intro to Async in Rust

Image description

Let’s break down this async tea-making example step-by-step to clarify what it demonstrates about asynchronous programming in Rust with tokio. Here’s a clear walkthrough of the main concepts and what each part of the code is teaching:

Goal of the Code Example

This example illustrates concurrent task management using async/await in Rust, specifically using tokio. It shows how to run tasks in parallel rather than sequentially, making the program more efficient by not waiting around for tasks that can run independently.

Key Concepts This Code Teaches

  1. Asynchronous Function Basics

    Each function that performs an asynchronous operation (like boil_water and get_tea_bag) is defined with the async keyword. These functions are like "promises" that let the program do other work while they’re waiting on time-consuming tasks (like boiling water).

  2. Running Tasks Concurrently
    By starting the boil_water task before getting the tea bag, we’re able to do two things at once:

    • boil_water() takes time, but we can let it run without blocking the main program.
    • Meanwhile, we’re free to do other things, like get_tea_bag(), while boil_water completes in the background.

This approach is more efficient than waiting for each step to complete before moving to the next one.

  1. Using .await to Control Execution Order await is used to control when each asynchronous task is completed:
    • Starting the Water: let water_boiling = boil_water(); starts the boil_water task asynchronously but doesn’t wait for it.
    • Getting the Tea Bag: get_tea_bag().await; waits for this task to complete.
    • Waiting for the Water to Boil: water_boiling.await; waits for the boil_water task to complete before continuing.

By not awaiting boil_water right away, we let it run in the background, making the program faster overall.

  1. Real-World Scenario: Non-Blocking I/O This simulates real-world asynchronous programming scenarios, like handling I/O requests in a web server:
    • While waiting for one task to finish (e.g., a slow API request or database query), you can handle other tasks concurrently, maximizing efficiency.

Code Walkthrough with Explanations

Here’s a breakdown of each section and what it’s teaching:

#[tokio::main]
async fn main() {
Enter fullscreen mode Exit fullscreen mode
  • #[tokio::main]: This attribute sets up the async runtime, allowing you to use async/await directly in main.
  • async fn main(): The main function is asynchronous, meaning it can manage multiple tasks concurrently.
    println!("Starting to make tea...");
    let water_boiling = boil_water();
Enter fullscreen mode Exit fullscreen mode
  • Starting a Task: This line initiates the boil_water function without waiting for it to finish. The program is free to do other work while the water is boiling.
    println!("Getting tea bag while water boils...");
    get_tea_bag().await;
Enter fullscreen mode Exit fullscreen mode
  • Awaiting get_tea_bag: Since getting a tea bag is a quick task, we await it immediately. The program waits for get_tea_bag() to complete before moving to the next line, but it’s fine because we don’t need it to run concurrently with anything else.
    water_boiling.await;
Enter fullscreen mode Exit fullscreen mode
  • Awaiting water_boiling Completion: This line tells the program to wait for boil_water() to finish. By this point, get_tea_bag() is already done, so this is the last step before the tea is ready.

Visualizing the Execution Order

Think of it like a checklist where you can do some tasks simultaneously:

  1. Start Boiling Waterboil_water() starts but doesn’t block other work.
  2. Get Tea Bag → Runs to completion with .await.
  3. Wait for Waterwater_boiling.await; ensures we don’t move forward until the water is ready.

Final Takeaways

  • Non-blocking Design: By leveraging async functions, you create a non-blocking design, allowing the program to continue working on other tasks instead of waiting idly.
  • Concurrency for Efficiency: This approach is efficient and useful in real-world scenarios, like handling multiple requests at once in a server.
  • Flexible Task Control: The program has precise control over which tasks run together and when to wait, making it both flexible and efficient.

This tea-making example illustrates a simple, relatable way to think about concurrency and asynchronous execution in Rust using tokio. Instead of waiting sequentially for each task to finish, you’re free to organize your tasks in a way that lets the program do as much as possible in parallel, saving time and resources.

Ben Santora - October 2024

Top comments (0)