Skip to content

01 Async/Await

Goal

Write asynchronous code with async/await, understand Future, and use the Tokio runtime effectively.

The Future Trait

rust
trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

enum Poll<T> {
    Ready(T),
    Pending,
}

async/await Basics

rust
async fn fetch_data() -> String {
    // Simulates async work
    tokio::time::sleep(Duration::from_millis(100)).await;
    "data".to_string()
}

async fn process() {
    let data = fetch_data().await; // suspends until ready
    println!("Got: {}", data);
}

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

Running Multiple Futures Concurrently

rust
use tokio::join;

async fn fetch_user(id: u32) -> String {
    tokio::time::sleep(Duration::from_millis(100)).await;
    format!("User {}", id)
}

async fn fetch_posts(user_id: u32) -> Vec<String> {
    tokio::time::sleep(Duration::from_millis(100)).await;
    vec!["post1".into(), "post2".into()]
}

async fn main() {
    // Sequential (slow)
    let user = fetch_user(1).await;
    let posts = fetch_posts(1).await;
    
    // Concurrent (fast) - join!
    let (user, posts) = join!(fetch_user(1), fetch_posts(1));
    
    // Concurrent with many - join_all
    let users: Vec<_> = (1..=10).map(fetch_user).collect();
    let results = futures::future::join_all(users).await;
}

select! — Race Multiple Futures

rust
use tokio::select;

async fn race() {
    let slow = async {
        tokio::time::sleep(Duration::from_secs(10)).await;
        "slow"
    };
    
    let fast = async {
        tokio::time::sleep(Duration::from_millis(100)).await;
        "fast"
    };
    
    select! {
        result = slow => println!("Slow won: {}", result),
        result = fast => println!("Fast won: {}", result),
    }
}

Spawning Tasks

rust
#[tokio::main]
async fn main() {
    // Spawn on runtime - runs concurrently
    let handle = tokio::spawn(async {
        tokio::time::sleep(Duration::from_millis(500)).await;
        println!("Background task done");
    });
    
    // Do other work...
    println!("Main continues");
    
    // Wait for spawned task
    handle.await.unwrap();
}

Async Streams

rust
use futures::stream::{self, StreamExt};

async fn stream_example() {
    let stream = stream::iter(1..=5)
        .then(|x| async move {
            tokio::time::sleep(Duration::from_millis(100)).await;
            x * 2
        });
    
    // Collect all
    let results: Vec<_> = stream.collect().await;
    println!("{:?}", results); // [2, 4, 6, 8, 10]
}

Channels — Communicating Between Tasks

rust
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel::<String>(32);
    
    // Producer
    tokio::spawn(async move {
        for i in 0..10 {
            tx.send(format!("message {}", i)).await.unwrap();
        }
    });
    
    // Consumer
    while let Some(msg) = rx.recv().await {
        println!("Received: {}", msg);
    }
}

Shared State — Arc<Mutex<T>> vs Arc<RwLock<T>>

rust
use std::sync::{Arc, Mutex};
use tokio::sync::Mutex as AsyncMutex;

#[tokio::main]
async fn main() {
    // For async code, prefer async Mutex
    let data = Arc::new(AsyncMutex::new(Vec::new()));
    
    let mut handles = vec![];
    for i in 0..10 {
        let data = Arc::clone(&data);
        handles.push(tokio::spawn(async move {
            let mut lock = data.lock().await;
            lock.push(i);
        }));
    }
    
    for h in handles { h.await.unwrap(); }
    println!("{:?}", data.lock().await);
}

Timeout and Cancellation

rust
use tokio::time::{timeout, Duration};

async fn slow_operation() -> String {
    tokio::time::sleep(Duration::from_secs(10)).await;
    "done".to_string()
}

#[tokio::main]
async fn main() {
    match timeout(Duration::from_secs(1), slow_operation()).await {
        Ok(result) => println!("Success: {}", result),
        Err(_) => println!("Timed out!"),
    }
}

Checkpoint

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

async fn fetch(id: u32) -> String {
    sleep(Duration::from_millis(100)).await;
    format!("Item {}", id)
}

#[tokio::main]
async fn main() {
    // Concurrent fetch
    let (a, b, c) = join!(fetch(1), fetch(2), fetch(3));
    println!("{}, {}, {}", a, b, c);
    
    // Stream processing
    use futures::stream::StreamExt;
    let stream = futures::stream::iter(1..=5)
        .map(fetch)
        .buffer_unordered(3); // max 3 concurrent
    
    let items: Vec<_> = stream.collect().await;
    println!("Stream: {:?}", items);
}

Next

Continue to 02 Concurrency Primitives.