Confusion about "async fn" and "fn -> impl Future..."

Thought that async fn() and fn() -> impl Future<...> are the same, i.e both are async functions?
However, the it seems that the latter does not allow await calls.
Questions:

  1. Are the two the same or equivalent?
  2. Why is await not allowed in fn do_async() ... ?

See sample code & error message below:

// is do_async() an async fn (because it returns a 'impl Future')?
fn do_async() -> impl Future<Output=u32> {
    let _j = async { 5 }.await;  // doesn't compile  (error below) - Why??
    async {10}
}

async fn do_another_async() -> u32 {
    let _j = async { 5 }.await;  // same as above but complies, not issues
    10
}

Compilation Error here:

error[E0728]: `await` is only allowed inside `async` functions and blocks
|
22 | fn do_async() -> impl Future<Output=u32> {
    |    -------- this is not `async`
    23 |     let _j = async { 5 }.await;  // doesn't compile - why?
    |              ^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks
async fn fname(args…) -> Ret {
    body…
}

is sugar for:

fn fname(args…) -> impl 'args + Future<Output = Ret> {
    /* return */ async move {
        let _ = &args…; /* ensure all the args are captured */
        body…
    }
}
  • where 'args is a hidden generic lifetime parameter which represents the intersection of all the regions of code where each arg can be used, so that the returned `Future` is conservativley only usable there.
    fn fname<'args, …> (
        arg1: Arg1,
        arg2: Arg2,
    ) -> impl 'args + Future<Output = Ret>
    where
        Arg1 : 'args,
        Arg2 : 'args,
    {
        /* return */ async move {
            let _ = &args…; /* ensure all the args are captured */
            body…
        }
    }
    

You can try to use the showme feature of:

to have a proc-macro perform that unsugaring for you.


Relatedly, if you just want to be able to use -> impl Future… in the return type without having to wrap the whole function body within an async move { … }, you can use:

and it's #[bare_future] attribute (also named #[barefoot] :upside_down_face:):

use ::async_fn::prelude::*;

#[bare_future]
async fn do_async() -> impl Fut<'static, u32> /* + Send */ {
    let _j = async { 5 }.await; // OK
    10
}
  • (You can add the + Send there to have Rust ensure your future is thread-safe, which is very often required (In that regard, I cannot recommend enough that #![deny(clippy::future_not_send)] be used in most codebases).)
1 Like

I don't understand how lifetimes affect this and the error doesn't complain about lifetimes, but says "await can only be used in async functions & block"

Isn't fn() -> impl Future<...> an async function?

In your example with bare_future, it's declared the same (with the preceding async) - this will make it equivalent to my async fn do_another_async() -> u32 which i know can compile with the await.

I am trying to avoid using 'async fn ...' in traits - i.e. need to use the async_trait crate and macro.

Your example showcased no lifetimes, but I just wanted to give the general answer.

The thing in Rust is that you can only use .await when inside an async context, which we could say ends up being an async [move] { … } block.

An async fn just happens to wrap its body in an implicit such block, whereas your "unsugared" code has no async { … } block around the .await you attempted to use.

In your case, the correct unsugaring would thus be:

fn do_async() -> impl 'static + Future<Output = u32> {
    async move {
        let _j = async { 5 }.await;
        10
    }
}

Feel free to look at the dropdown explanation in ::async_fn to more examples and nuances regarding the unsugaring.

Ok, i am confused and/or don't understand something fundamental here. Strangely, in the sample main() below, the ide/compiler/rust-checker? says a and b are the same type and i need to call await on them - so i still don't get (don't understand) what's the difference :

#[tokio::main]
async fn main() {
    let a = do_another_async();  // variable a: impl Future<Output=u32>
    let a = a.await;

    let b = do_async(); // variable b: impl Future<Output=u32>
    let b = b.await;

    println!("a: {}; b: {}", a, b);
}

main is an async fn, so you can use .await in it.

do_async is not an async fn, just a fn that returns a future. So you cannot use .await in its definition, unless you add an async block. But you can still .await its result inside an async function or block, because it still returns a future.

async is not "infectious" across functions. Just because you call do_async from inside an async fn does not mean that do_async itself becomes an async fn.

What async fn or async [move] {...} do for you is create a state machine, and the .await points inside the function or block define the transition points between states. This is why .await doesn't make sense outside of an async function or block: there's no state machine to affect.

Thanks, let me chew on this a bit to try to understand ... probably will take a couple of years!

1 Like

Thanks for confirming that fn() -> impl Future<> is not an async fn, but simply returns a Future.

Is there some nice (noob-readable) literature you can recommend that clarifies this?

Honestly, not really. I learned about async mostly by following the RFCs and reading the occasional blog post; I've never even used it in a non-trivial program. But the async book is probably a good place to start.

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.