6

In JavaScript, async code is written with Promises and async/await syntax similar to that of Rust. It is generally considered redundant (and therefore discouraged) to return and await a Promise when it can simply be returned (i.e., when an async function is executed as the last thing in another function):

async function myFn() { /* ... */ }

async function myFn2() {
  // do setup work

  return await myFn()
  // ^ this is not necessary when we can just return the Promise
}

I am wondering whether a similar pattern applies in Rust. Should I prefer this:

pub async fn my_function(
    &mut self,
) -> Result<()> {
    // do synchronous setup work

    self.exec_command(
        /* ... */
    )
    .await
}

Or this:

pub fn my_function(
    &mut self,
) -> impl Future<Output = Result<()>> {
    // do synchronous setup work

    self.exec_command(
        /* ... */
    )
}

The former feels more ergonomic to me, but I suspect that the latter might be more performant. Is this the case?

laptou
  • 6,389
  • 2
  • 28
  • 59
  • 3
    I expected both to compile to identical code, so I tried on godbolt. When I turn [optimizations on](https://godbolt.org/z/j2k_YX) both functions are identical but mostly empty (and look no longer async (?)), and when I turn [optimization off](https://godbolt.org/z/YdJb6z) there are differences but I can't tell how well they would be optimized in real code… – Jmb Jan 09 '20 at 07:32
  • 3
    I think `async fn` adds some auto traits (like `Send` or `Unpin`) if possible. Returning `impl Future` yourself means you're responsible for keeping track of that in the return type. Either way I'd expect these to generate the same assembly in release mode. – Frxstrem Jan 09 '20 at 07:40
  • 1
    @Jmb That's interesting. If I make the code [slightly more complicated](https://godbolt.org/z/bjCCeG), the version using the `impl Future` seems to generate a lot more instructions. – laptou Jan 09 '20 at 23:57
  • See also [What is the purpose of async/await in Rust?](https://stackoverflow.com/q/52835725/155423) – Shepmaster Jan 10 '20 at 16:37

2 Answers2

3

One semantic difference between the two variants is that in the first variant the synchronous setup code will run only when the returned future is awaited, while in the second variant it will run as soon as the function is called:

let fut = x.my_function();
// in the second variant, the synchronous setup has finished by now
...
let val = fut.await;  // in the first variant, it runs here

For the difference to be noticeable, the synchronous setup code must have side effects, and there needs to be a delay between calling the async function and awaiting the future it returns.

Unless you have specific reason to execute the preamble immediately, go with the async function, i.e. the first variant. It makes the function slightly more predictable, and makes it easier to add more awaits later as the function is refactored.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user4815162342
  • 141,790
  • 18
  • 296
  • 355
1

There is no real difference between the two since async just resolves down to impl Future<Output=Result<T, E>>. I don't believe there is any meaningful performance difference between the two, at least in my empirical usage of both.

If you are asking for preference in style then in my opinion the first one is preferred as the types are clearer to me and I agree it is more ergonomic.

Stephen Carman
  • 999
  • 7
  • 25