iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

async / await

Rusts async story: async fn returns a Future that does no work until polled by an executor. Tokio is the standard executor for servers and CLIs; smol/embassy fit smaller targets. Async unlocks high-concurrency I/O with very little memory per task, but the borrow checker, Send bounds, and lifetimes are stricter inside async than in sync code.

Concurrent fetches, timeouts, channels, with Tokio

EXAMPLE
// Cargo.toml
// [dependencies]
// tokio = { version = "1", features = ["full"] }
// reqwest = "0.12"
// futures = "0.3"

use std::time::Duration;
use tokio::{time, sync::mpsc};
use futures::future::join_all;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1) Run several fetches concurrently
    let urls = vec![
        "https://example.com",
        "https://example.org",
        "https://example.net",
    ];
    let client = reqwest::Client::new();

    let bodies = join_all(urls.into_iter().map(|u| {
        let client = client.clone();
        async move {
            let res = time::timeout(Duration::from_secs(5), client.get(u).send()).await??;
            let bytes = res.bytes().await?;
            Ok::<_, Box<dyn std::error::Error + Send + Sync>>(bytes.len())
        }
    })).await;

    for (i, r) in bodies.into_iter().enumerate() {
        println!("{i}: {:?}", r.map(|n| format!("{n} bytes")));
    }

    // 2) Producer/consumer via mpsc — back-pressure built in
    let (tx, mut rx) = mpsc::channel::<u32>(16);
    let producer = tokio::spawn(async move {
        for i in 0..10 {
            tx.send(i).await.unwrap();
            time::sleep(Duration::from_millis(50)).await;
        }
    });
    let consumer = tokio::spawn(async move {
        while let Some(n) = rx.recv().await {
            println!("got {n}");
        }
    });
    let _ = tokio::try_join!(producer, consumer)?;

    // 3) Select on multiple futures — first one to finish wins
    let result = tokio::select! {
        v = slow_op() => format!("slow: {v}"),
        _ = time::sleep(Duration::from_millis(100)) => "timed out".to_string(),
    };
    println!("{result}");

    // 4) CPU-bound work inside async: hand it off to a blocking thread
    let answer = tokio::task::spawn_blocking(|| {
        let mut sum: u64 = 0;
        for i in 0..1_000_000_u64 { sum = sum.wrapping_add(i.wrapping_mul(31)); }
        sum
    }).await?;
    println!("answer={answer}");

    Ok(())
}

async fn slow_op() -> u32 {
    time::sleep(Duration::from_millis(250)).await;
    42
}

Why it matters

CPU-bound work inside async (image resize, JSON parse of huge blob, hashing) stalls the executor and tanks tail latency. Wrap it in spawn_blocking so it runs on the blocking pool, then await the JoinHandle — async stays responsive while the heavy work proceeds.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
async fn fetch_id() -> u32 { 42 }

#[tokio::main]
async fn main() {
    let id = fetch_id().await;
    println!("id = {id}");
}
Try it Yourself »

Discussion

Loading…