Skip to content

10 Async Programming

Goal

Understand async/await, Future, runtimes (Tokio), streams, and async patterns.

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 {
    // Simulate async work
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    "data".to_string()
}

#[tokio::main]
async fn main() {
    let data = fetch_data().await;
    println!("Got: {}", data);
}

.await Points

rust
async fn example() {
    let a = fetch_a().await;  // Suspension point
    let b = fetch_b().await;  // Suspension point
    println!("{} {}", a, b);
}

Concurrent Execution

join! — Run Concurrently

rust
use tokio::join;

async fn fetch_a() -> String {
    tokio::time::sleep(Duration::from_millis(100)).await;
    "a".to_string()
}

async fn fetch_b() -> String {
    tokio::time::sleep(Duration::from_millis(200)).await;
    "b".to_string()
}

async fn main() {
    let (a, b) = join!(fetch_a(), fetch_b());
    // Takes ~200ms total, not 300ms
}

select! — Race Futures

rust
use tokio::select;

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

try_join! — Error Handling

rust
use tokio::try_join;

async fn fetch(id: u32) -> Result<String, Error> {
    if id == 0 { return Err(Error::new("invalid")); }
    Ok(format!("data {}", id))
}

async fn main() -> Result<(), Error> {
    let (a, b) = try_join!(fetch(1), fetch(2))?;
    Ok(())
}

Spawning Tasks

rust
#[tokio::main]
async fn main() {
    // Spawn on runtime
    let handle = tokio::spawn(async {
        tokio::time::sleep(Duration::from_millis(100)).await;
        "task result"
    });
    
    // Do other work...
    
    let result = handle.await.unwrap();
}

Channels

rust
use tokio::sync::mpsc;

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

Shared State

rust
use tokio::sync::Mutex;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let data = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    
    for _ in 0..10 {
        let data = data.clone();
        handles.push(tokio::spawn(async move {
            let mut lock = data.lock().await;
            *lock += 1;
        }));
    }
    
    for h in handles { h.await.unwrap(); }
    println!("{}", *data.lock().await); // 10
}

Streams

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

async fn process_stream() {
    let stream = stream::iter(1..=5)
        .map(|x| async move { x * 2 })
        .buffer_unordered(3);
    
    let mut sum = 0;
    futures::pin_mut!(stream);
    while let Some(x) = stream.next().await {
        sum += x;
    }
    println!("Sum: {}", sum); // 30
}

Timeout

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_millis(100), slow_operation()).await {
        Ok(result) => println!("{}", result),
        Err(_) => println!("Timed out!"),
    }
}

Cancellation

rust
use tokio::time::{sleep, Duration};
use tokio_util::sync::CancellationToken;

async fn long_running(token: CancellationToken) {
    loop {
        tokio::select! {
            _ = token.cancelled() => {
                println!("Cancelled!");
                return;
            }
            _ = sleep(Duration::from_millis(100)) => {
                println!("Working...");
            }
        }
    }
}

#[tokio::main]
async fn main() {
    let token = CancellationToken::new();
    let handle = tokio::spawn(long_running(token.clone()));
    
    sleep(Duration::from_millis(500)).await;
    token.cancel();
    handle.await.unwrap();
}

Checkpoint

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

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

async fn fetch_posts(user_id: u32) -> Vec<String> {
    sleep(Duration::from_millis(200)).await;
    vec![format!("Post by {}", user_id)]
}

#[tokio::main]
async fn main() {
    // Concurrent fetches
    let (user, posts) = join!(
        fetch_user(42),
        fetch_posts(42)
    );
    
    println!("User: {}", user);
    println!("Posts: {:?}", posts);
    
    // Race with timeout
    let slow = async {
        sleep(Duration::from_secs(10)).await;
        "slow"
    };
    
    let fast = async {
        sleep(Duration::from_millis(50)).await;
        "fast"
    };
    
    let result = join!(slow, fast);
    println!("{:?}", result); // fast completes first
}

Next

Continue to 11 Standard Library Tour.