Rust Async Closures
An async closure is a closure whose call builds a future. Each call can be awaited like any future.
The smallest async closure
Define it like a closure, call it like a function, await the call like a future:
let fetch = async || {
String::from("data")
};
let data: String = fetch().await;
println!("{data}");It borrows โ so it can be called again
This closure borrows name. Because every call builds a new future, calling it twice is fine โ an async block cannot do this trick.
let name = String::from("Ada");
let greet = async || format!("hello, {name}");
println!("{}", greet().await);
println!("{}", greet().await);async move takes ownership
Add move and the closure takes its captures with it. The rule is the same as for sync closures. The reason is the same too: the future may outlive the current scope.
let owned = String::from("owned words");
let take = async move || format!("took: {owned}");
println!("{}", take().await);Take any async closure: the AsyncFn bound
AsyncFn is the async twin of the Fn trait. Bound a generic on it when a helper accepts any async closure. The closure must have a given call shape. Here: no arguments, returns a String.
use std::ops::AsyncFn;
async fn run_twice<F>(f: F) -> (String, String)
where
F: AsyncFn() -> String,
{
let first = f().await;
let second = f().await;
(first, second)
}Example
use std::ops::AsyncFn;
async fn run_twice<F>(f: F) -> (String, String)
where
F: AsyncFn() -> String,
{
let first = f().await;
let second = f().await;
(first, second)
}
#[tokio::main]
async fn main() {
let greeting = String::from("hello");
// an async closure: it borrows `greeting` and can be called again and again
let greeter = async || format!("{greeting}, async world");
let (one, two) = run_twice(greeter).await;
println!("{one}");
println!("{two}");
// the closure only borrowed the String -- it is still ours
println!("still mine: {greeting}");
}Key points
- Syntax is one word more than a normal closure.
- Write let f = async || { ... } to define one.
- Calling it returns a future.
- You await that future: f().await.
- Async closures capture exactly like normal closures: by reference, by mutable reference, or by move with async move ||.
- Each call makes a FRESH future, and that future may borrow what the closure captured.
- That is the big difference from an async block, which is a single future you can only await once.
- The Fn family has async twins in std::ops: AsyncFn, AsyncFnMut, AsyncFnOnce.
- Use them as bounds when a helper should accept any async closure.
- Older code faked the same thing with Fn() -> impl Future bounds.
- When you meet that shape in a library, read it as 'an async closure goes here'.
- Async closures arrived in Rust 1.85.
The closure borrows greeting. run_twice calls it twice through the AsyncFn bound. Each call makes a fresh future over the same borrowed String. Because nothing was moved, greeting is still fully usable after both calls.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Rust Playground
Exercise
What does calling an async closure give you?