Skip to content

Latest commit

 

History

History
589 lines (405 loc) · 16.4 KB

18_asynchronous_programming.md

File metadata and controls

589 lines (405 loc) · 16.4 KB

🦀 30 Days of Rust: Day 18 - Asynchronous Programming in Rust 🚀

LinkedIn Follow me on GitHub

Author: Het Patel

October, 2024

<< Day 17 | Day 19 >>

30DaysOfRust


📘 Day 18 - Asynchronous Programming in Rust

👋 Welcome

Welcome to Day 18 of the 30 Days of Rust Challenge! 🎉

Today’s focus is Asynchronous Programming in Rust—a critical paradigm for building modern, efficient, and scalable systems. Async programming is all about handling tasks like I/O, event-driven code, and network communication without blocking execution. Rust offers a unique async model powered by its zero-cost abstractions and compile-time guarantees for safety.

🔍 Overview

Why Async Programming?

Asynchronous programming allows tasks to run concurrently without blocking the main thread. This approach is especially useful for:

Async programming allows you to:

  1. Avoid Blocking: Handle I/O or timers without halting the program.
  2. Enable Concurrency: Execute multiple tasks simultaneously on fewer threads.
  3. Achieve Scalability: Ideal for applications like web servers, which handle thousands of requests.
  • I/O-bound tasks (e.g., HTTP requests, file reading).
  • Event-driven systems.
  • Applications requiring high scalability.

When Should You Use Async Programming?

  • Best Use Cases:

    • Network-intensive applications (HTTP clients/servers).
    • Programs with idle time (e.g., waiting for I/O responses).
  • Avoid for CPU-bound Tasks:

    • Async is not suitable for heavy computations. Use multi-threading instead for true parallelism.

Key Difference from Multi-threading:

Feature Multi-threading Asynchronous Programming
Performance High overhead for threads Lower overhead
Best for CPU-intensive tasks I/O-bound or idle tasks
Complexity Potential for race conditions Safe at compile time

By the end of this lesson, you’ll:

  • Understand the async and await syntax.
  • Learn about Futures and their role in async programming.
  • Use executors like Tokio to run async code.
  • Combine and manage multiple async tasks with utilities like join! and select!.

🛠 Environment Setup

Ensure you have Cargo installed and are working with Rust 1.39 or later (the version that introduced async/await). If not, update Rust using:

rustup update

For today’s lessons, we’ll also need the Tokio crate. Add it to your project with:

cargo add tokio --features full

To work with async programming in Rust, you’ll need an async runtime. We'll use Tokio, the most popular runtime.

Step 1: Add Dependencies

Add this to your Cargo.toml:

[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }  # Optional for HTTP

Step 2: Verify the Setup

Run the following command to ensure your dependencies are installed:

cargo build

⚙ Understanding Asynchronous Programming

Why Asynchronous Programming?

  • Efficiency: It uses fewer threads while handling many tasks.
  • Scalability: Ideal for applications like web servers that need to handle thousands of connections.

Sync vs Async

Synchronous Asynchronous
Blocks the thread. Does not block threads.
Simpler to write. More scalable.

⚙ Fundamentals of Asynchronous Programming

Why Async Programming?

  • Traditional Approach: In synchronous programming, tasks are executed one after another. If a task waits for an operation (e.g., reading a file), the entire thread is blocked.
  • Async Approach: Tasks yield control during waits, allowing other tasks to execute.

Example comparison:

Synchronous:

use std::thread::sleep;  
use std::time::Duration;  

fn main() {  
    println!("Task 1 started!");  
    sleep(Duration::from_secs(2));  
    println!("Task 1 completed!");  

    println!("Task 2 started!");  
    sleep(Duration::from_secs(1));  
    println!("Task 2 completed!");  
}  

Asynchronous:

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

#[tokio::main]  
async fn main() {  
    let task1 = async {  
        println!("Task 1 started!");  
        sleep(Duration::from_secs(2)).await;  
        println!("Task 1 completed!");  
    };  

    let task2 = async {  
        println!("Task 2 started!");  
        sleep(Duration::from_secs(1)).await;  
        println!("Task 2 completed!");  
    };  

    tokio::join!(task1, task2);  
}  

Output: Both tasks run concurrently, reducing total execution time.

⚙ Asynchronous Programming Basics

🕸 Async in Rust

Rust async programming revolves around three core concepts:

  1. async functions.
  2. await expressions.
  3. Future trait.

🔧 Understanding async and await

  • async: Marks a function as asynchronous.
  • await: Waits for a Future to complete.

Example

async fn add(a: u8, b: u8) -> u8 {  
    a + b  
}  

#[tokio::main]  
async fn main() {  
    let result = add(10, 20).await;  
    println!("Result: {}", result);  
}  

or

🔧 async and await

  • async fn: Marks a function as asynchronous, returning a Future.
  • .await: Waits for the completion of a Future.

Example:

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

#[tokio::main]  
async fn main() {  
    hello_world().await;  
}  

📚 Futures and Their Role

  • What is a Future?
    A Future is a placeholder for a value that may not yet exist.

    A Future in Rust represents a value that may not yet be available.

  • Lazy Execution: A Future does nothing until .await is called.

  • Lazy Execution: Futures do nothing until explicitly awaited.

  • Polling States:

    • Poll::Pending: Task in progress.
    • Poll::Ready: Task completed.
  • Polled by Executors: Executors like Tokio drive futures to completion.

Example: Returning a Future

async fn compute() -> u32 {
    42
}

#[tokio::main]
async fn main() {
    let result = compute().await;
    println!("Result: {}", result);
}

💡 Rust's Async Model

Rust’s async model is built around:

  • Futures: A computation that will produce a value at some point in the future.
  • async/await: Keywords for writing async code in a synchronous style.
  • Executors: Runtime systems like Tokio that poll futures to completion.

📜 Background: Future and async/await

In Rust:

  • Future: A trait representing an asynchronous computation.
  • async: Turns a function into one that returns a Future.
  • await: Suspends execution until a Future is ready.

Example:

use tokio::time;

#[tokio::main]
async fn main() {
    println!("Task started...");
    time::sleep(time::Duration::from_secs(2)).await;
    println!("Task completed!");
}

🔄 Executors and Their Importance

Executors are responsible for running async code. They manage task scheduling and drive Futures to completion.

Async code requires an executor to drive futures to completion. Common executors:

  • Tokio: High-performance async runtime.
  • async-std: Async-friendly standard library.

Tokio example:

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

async fn task() {  
    sleep(Duration::from_secs(1)).await;  
    println!("Task completed!");  
}  

#[tokio::main]  
async fn main() {  
    task().await;  
}  

💬 Tasks and Executors

To run async functions, we need an executor. Executors like Tokio or async-std poll futures until they’re complete.

🔧 Using Tokio and async-std

Using Tokio

Tokio is the most popular async runtime in Rust:

use tokio::time;

#[tokio::main]
async fn main() {
    println!("Fetching data...");
    time::sleep(time::Duration::from_secs(2)).await;
    println!("Data fetched!");
}

Using async-std

Async-std is another runtime for async programming:

use async_std::task;

#[async_std::main]
async fn main() {
    println!("Processing...");
    task::sleep(std::time::Duration::from_secs(1)).await;
    println!("Done!");
}

🔀 Combining and Managing Async Tasks

🔀 Combining Futures with join! and select!

  • join!: Runs multiple futures concurrently, waiting for all to complete.
  • select!: Waits for the first future to complete.

Example:

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

async fn task1() {  
    sleep(Duration::from_secs(2)).await;  
    println!("Task 1 done!");  
}  

async fn task2() {  
    sleep(Duration::from_secs(1)).await;  
    println!("Task 2 done!");  
}  

#[tokio::main]  
async fn main() {  
    tokio::join!(task1(), task2());  
}  

Output:

Task 2 done!  
Task 1 done!  

join!: Run Multiple Futures Concurrently

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

async fn task1() {  
    sleep(Duration::from_secs(2)).await;  
    println!("Task 1 done!");  
}  

async fn task2() {  
    sleep(Duration::from_secs(1)).await;  
    println!("Task 2 done!");  
}  

#[tokio::main]  
async fn main() {  
    tokio::join!(task1(), task2());  
}  

select!: Wait for the First Completed Future

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

#[tokio::main]  
async fn main() {  
    tokio::select! {  
        _ = sleep(Duration::from_secs(2)) => println!("2 seconds passed"),  
        _ = sleep(Duration::from_secs(1)) => println!("1 second passed"),  
    };  
}  

📖 Real-World Example: HTTP Fetcher

Let’s build a simple HTTP fetcher using the reqwest library.

Add reqwest Dependency

Include the following in Cargo.toml:

[dependencies]
reqwest = { version = "0.11", features = ["json"] }

Example Code

use reqwest::Error;

async fn fetch_url(url: &str) -> Result<(), Error> {
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    println!("Response: {}", body);
    Ok(())
}

#[tokio::main]
async fn main() {
    let url = "https://jsonplaceholder.typicode.com/posts/1";
    if let Err(e) = fetch_url(url).await {
        eprintln!("Error: {}", e);
    }
}

or

Example

use reqwest::Error;  

async fn fetch_url(url: &str) -> Result<(), Error> {  
    let response = reqwest::get(url).await?;  
    println!("Response: {}", response.text().await?);  
    Ok(())  
}  

#[tokio::main]  
async fn main() {  
    fetch_url("https://jsonplaceholder.typicode.com/posts/1").await.unwrap();  
}  

🚀 Hands-On Challenge

  1. Write an async program that:
    • Fetches data from three APIs concurrently.
    • Logs the fastest response using select!.
  2. Implement error handling for async functions.
  3. Write a program with multiple async tasks using join!.
  4. Use select! to handle the first completed task.
  5. Implement an async function that makes multiple HTTP requests concurrently (use reqwest library).

💻 Exercises - Day 18

✅ Exercise: Level 1

  1. Write an async program that:

    • Prints "Starting task...".
    • Waits for 3 seconds using async-std or Tokio.
    • Prints "Task completed!".
  2. Implement a function async_fetch that simulates fetching data from a server and returns the string "Data received!".

🚀 Exercise: Level 2

  1. Async Web Server:

    • Use tokio to build a simple web server that responds to HTTP requests with "Hello, Rust Async!".
  2. Concurrent Tasks:

    • Create a program that spawns three async tasks, each waiting for a random amount of time before printing its completion.
  3. Async File I/O:

    • Write an async function to read a file’s content and print it to the console.

🎥 Additional Resources

📚 More Insights

async/.await in Rust

Async functions return Futures. Use .await to pause execution until a Future is ready.

Example:

async fn add(a: u8, b: u8) -> u8 {
    a + b
}

#[tokio::main]
async fn main() {
    let result = add(2, 3).await;
    println!("Sum: {}", result);
}

Alternatives for Running Async Code

  1. Tokio: Adds async capabilities with #[tokio::main].
  2. Futures Library: Provides block_on to block until a Future is ready.

📝 Day 18 Summary

Today, you’ve mastered:

  • Async basics: async, await, and Futures.
  • Task management: Combining tasks with join! and select!.
  • Executors: Running async code efficiently.
  • Real-world use cases: HTTP requests and concurrency patterns.

Stay tuned for Day 19, where we will explore Networking in Rust in Rust! 🚀

🌟 Great job on completing Day 18! Keep practicing, and get ready for Day 19!

Thank you for joining Day 18 of the 30 Days of Rust challenge! If you found this helpful, don’t forget to Star GIF star this repository, share it with your friends, and stay tuned for more exciting lessons ahead!

Stay Connected
📧 Email: Hunterdii
🐦 Twitter: @HetPate94938685
🌐 Website: Working On It(Temporary)

<< Day 17 | Day 19 >>