Tokio

#041 Mar 2026

41. Async Closures — Pass Async Code Like Any Other Closure

Accepting an async callback used to mean a tangle of Fn(T) -> Fut where Fut: Future. Rust 1.85 stabilizes async closures — write async |x| { ... } and accept them with impl async Fn(T) -> U.

The old workaround

Before Rust 1.85, accepting an async function as a parameter meant spelling out the future type explicitly:

1
2
3
4
5
6
7
async fn run<F, Fut>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> Fut,
    Fut: std::future::Future<Output = i32>,
{
    f(x).await
}

It compiles, but the signature is noisy. It gets worse once you need FnMut, higher-ranked lifetimes, or closures that borrow from their captures.

The new way

1
2
3
4
5
6
7
async fn run(f: impl AsyncFn(i32) -> i32, x: i32) -> i32 {
    f(x).await
}

let double = async |x: i32| x * 2;
let result = run(double, 21).await;
assert_eq!(result, 42);

async |...| { ... } is the syntax for an async closure. Use AsyncFn(T) -> U bounds at call sites — AsyncFn, AsyncFnMut, and AsyncFnOnce mirror the regular Fn family and were stabilized alongside async closures in Rust 1.85.

Capturing state works naturally

Async closures capture their environment exactly like regular closures:

1
2
3
4
let base = 10_i32;
let adder = async |x: i32| base + x;

assert_eq!(adder(32).await, 42);

No extra boxing or lifetime gymnastics — the closure borrows base just as a sync closure would.

Apply it twice

1
2
3
4
5
6
async fn apply_twice(f: impl AsyncFn(i32) -> i32, x: i32) -> i32 {
    f(f(x).await).await
}

let double = async |x: i32| x * 2;
assert_eq!(apply_twice(double, 5).await, 20); // 5 → 10 → 20

Why it matters

The real payoff is in generic async APIs — retry helpers, middleware, event hooks — anywhere you’d pass a callback. Instead of Pin<Box<dyn Future>> boilerplate you get a clean bound:

1
2
3
4
5
6
7
8
async fn retry(op: impl AsyncFn() -> Result<i32, String>, tries: u32) -> Option<i32> {
    for _ in 0..tries {
        if let Ok(val) = op().await {
            return Some(val);
        }
    }
    None
}

Async closures are available in Rust 1.85+ (stable since February 2025). Make sure your crate uses edition = "2021" or later.