Asynchronous Rust

Async Closures - Language-Version Considerations

An async closure—written async |args| { ... } or async move |args| { ... } (stabilized in Rust 1.85 alongside the Rust 2024 edition)—is a closure that returns a future allowed to borrow directly from the closure's own captured environment across .await points, governed by the AsyncFn, AsyncFnMut, and AsyncFnOnce traits in the standard prelude.

Why Ordinary |x| async { ... } Closures Could Not Borrow Captures Mutably

Prior to Rust 1.85, writing |x| async { ... } created a regular synchronous closure (Fn / FnMut) whose return type happened to be an async block (F: FnMut(Arg) -> Fut, Fut: Future).

However, in FnMut::call_mut(&mut self, args) -> Self::Output, the return type Self::Output (Fut) cannot borrow from &mut self because FnMut's signature has no Generic Associated Type lifetime tying Self::Output to &'a mut self! Attempting to return an async block that borrowed a captured variable from a Fn or FnMut closure failed to compile:

1. Pre-1.85 Workaround (`|x| async move { ... }` with `F: Fn(Arg) -> Fut`):
   `Fn::call(&self)` returns `Fut` (which must be independent of `&self`!).
   ---> Forced cloning `Arc<T>` on every call or could not borrow `&mut` captures!

2. Native Async Closure (`async |x| { ... }` with `AsyncFnMut` — Rust 1.85+ / 2024):
   Returned future is a Lending GAT `CallRefFuture<'a>` tied to `&'a mut self`!
   ---> Can borrow and mutate captured local variables across `.await` points!

Evolution of Async Closure Support Across Rust Versions

Rust Version Syntax & Trait Bound Capabilities & Limitations
Rust 1.39 – 1.84 |x| async move { ... } bounded by F: FnMut(A) -> Fut, Fut: Future<Output = R> Returned future Fut cannot borrow from the closure's captured state (requires Arc::clone per call)
Rust 1.85+ (Rust 2024 Edition) async |x| { ... } bounded by F: AsyncFn(A) -> R / AsyncFnMut(A) -> R / AsyncFnOnce(A) -> R Returned future can borrow &self or &mut self captures across .await; single trait bound (no separate Fut type parameter needed!)

Using Native async || Closures with AsyncFnMut

With AsyncFn, AsyncFnMut, and AsyncFnOnce in the standard prelude, higher-order async functions take a single trait bound F: AsyncFnMut(T) -> U, and the async || closure can mutate captured local state across .await calls without Arc or Mutex:

use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, Waker};

async fn run_for_each<T, F>(items: &[T], mut callback: F)
where
    T: Copy,
    F: AsyncFnMut(T),
{
    for &item in items {
        callback(item).await;
    }
}

fn main() {
    let fut = async {
        // Captured local `Vec` mutated directly inside `async |n|` across `.await`!
        let mut history = Vec::new();

        run_for_each(&[10, 20, 30], async |n| {
            std::future::ready(()).await;
            history.push(n * 2);
        })
        .await;

        history
    };

    let mut pinned = pin!(fut);
    let mut cx = Context::from_waker(Waker::noop());
    println!("async closure history: {:?}", pinned.as_mut().poll(&mut cx));
}
async closure history: Ready([20, 40, 60])