|x| async move { ... } vs async move |x| { ... }

I am familiar with:

|x| { ... }
move |x| { ... }

I have tried

async move |x| { ... }

and gotten compiler error about experimental features.

What is:

|x| async move { ... }

?

1 Like

Refer to 2394-async_await - The Rust RFC Book where async || closures is not implemented for now.

Also see 💤 Async closures - async fn fundamentals initiative .

Refer to async book: async/await - Asynchronous Programming in Rust .

async { ... } is an async block, and async move { ... } is an async block that captures variables by-value.

So |x| async move { ... } is a closure that returns such an async block. You could also do move |x| async move { ... } though with non-copy types, moving in the async block will also move into the closure anyways.

3 Likes

Should |x| async move { ... } be interpreted as:

|x| {
  /* do nothing */
  async move { ... }
}

?

Yes. The closue returns an async block, and an async block is a constructor for a future. See "Async blocks vs async closures" in that RFC link.

3 Likes

I wonder if async closures are really needed (and for what), other than "consistency with async fn".

One reason I see is specifying a return type (unless there is some other way that's not workaroundy?):

#![allow(unused_variables)]
#![feature(async_closure)]

#[tokio::main]
async fn main() {
    // The non-async case:
    let closure = |x: std::io::Result<String>| -> anyhow::Result<()> {
        x?;
        Ok(())
    };
    // `impl` not allowed here:
    /*
    let closure_async_impl = |x: std::io::Result<String>| -> impl std::future::Future<Output = anyhow::Result<()>> {
        async {
            x?;
            Ok(())
        }
    };
    */
    // On stable we must do:
    let closure_async_turbofish = |x: std::io::Result<String>| async {
        x?;
        Ok::<_, anyhow::Error>(()) // workaround to specify the return type
    };
    // This is unstable, requires `feature(async_closure)`, and only works with `move`:
    let async_closure = async move |x: std::io::Result<String>| -> anyhow::Result<()> {
        x?;
        Ok(())
    };
}

(Playground)

The reason for async closure syntax is basically the same as the reason for async fn syntax, yes: to have different lifetime elision rules. Because the return type of closures are typically inferred rather than listed, this most often is a nonfunctional change.

I found this alternative way to specify a return type on stable:

fn main() {
    let _closure_async_id = |x: std::io::Result<String>| async {
        std::convert::identity::<anyhow::Result<()>>({
            x?;
            Ok(())
        })
    };
}

(Playground)

:thinking: I have to re-read about closures and lifetimes. It's kinda confusing to me (yet).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.