in the new blog post here you can read that until recently async closures desugared to normal closures returning a future. Furthermore there is written:
This had a fundamental limitation that it was impossible to express a closure that returns a future that borrows captured state.
I don't understand that as you can do for example:
#[tokio::main]
async fn main() {
let s = String::from("test");
let c = || { async {
println!("{s}");
}};
c().await;
}
So you can capture state or not? Could anybody explain please? Maybe I misunderstood it.
I'm not really sure, it seems like the main downside is that you usually want to move parameters into the async block but not always captures, which the syntax doesn't let you do. But that seems at least as expressible as normal closures, and is unrelated to the HRTB issue.
Your example closure doesn't return a future that borrows from the captured state. The closure captures &String and the future is created with a &String (not a &&String which would borrow from the state). So the future just copied the reference from the closure's state, it didn't borrow it. The mentioned issue would for example occur if the closure owned the string (by using move):
#[tokio::main]
async fn main() {
let s = String::from("test");
let c = move || { async {
println!("{s}");
}};
c().await;
}
A simpler example that shows the issue is that previously an async closure could not increment a count value but now it should be able to.
This fails to compile:
#[tokio::main]
async fn main() {
let mut count = 0;
let mut c = || { async {
count += 1;
}};
c().await;
dbg!(count);
}
#![feature(async_closure)]
#[tokio::main]
async fn main() {
let mut count = 0;
let mut c = async || {
count += 1;
};
c().await;
dbg!(count);
}
The reason that only async closures work is because they put more restrictions on how they can be called. A closure that returns a future can be called multiple times to get many futures and can then be dropped before those are awaited. This is not the case for async closures, there the closure remains borrowed while the future exists which prevents creating multiple futures from FnMut closures and ensures the closure state remains until the future is awaited. See the example code below:
pub async fn older<F, Fut>(mut f: F)
where
F: FnMut() -> Fut,
Fut: Future<Output = ()>,
{
let fut1 = f();
let fut2 = f();
// Could now have 2 `&mut u32` (one in each future)
drop(f);
// If a future borrowed a String owned by the closure then that string is now freed.
fut1.await;
fut2.await;
}
pub async fn newer<F>(mut f: F)
where
F: async FnMut(),
{
let fut1 = f();
// can't call `f` until `fut1` is dropped
// let fut2 = f();
// can't drop `f` before `fut1`
// drop(f);
fut1.await;
}