3

I am curious about how .await actually works in Rust. It doesn't seem to be part of the Future API, so I assume that it has to be some sort of compiler magic. How does .await work and what it can be applied to?

I understand how async construction works and how async functions are compiled using unstable generators; that is not my question. I am asking specifically about the .await magic member operator. That is, if we have an expression like

fut.await

What happens at this point and what constraints should fut satisfy for this expression to be valid?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
MrMobster
  • 1,851
  • 16
  • 25
  • Basically `async fn` is compiled to a state machine. See Tyler Mandry's blog posts [How Rust optimizes async/await I](https://tmandry.gitlab.io/blog/posts/optimizing-await-1/) and [II](https://tmandry.gitlab.io/blog/posts/optimizing-await-2/). – edwardw Dec 13 '19 at 12:43
  • The syntax let think that it's a variable member, but actually, it's an equivalent of `await {expression}` in the other languages. – Boiethios Dec 13 '19 at 12:55
  • @Shepmaster apologies if my question is valid. I agree that it is a bit broad but it basically boils down to *what is the semantics of an expression `x.await`*. If you think it is not appropriate for SO, I will happily move my question elsewhere – MrMobster Dec 13 '19 at 16:13
  • `await` is a keyword. It's not a member. The expression needs to implement the `Future` trait from `std`, which is a lang item, i.e. the compiler nows about it. Further details can be found in the [language reference](https://doc.rust-lang.org/stable/reference/expressions/await-expr.html). – Sven Marnach Dec 13 '19 at 21:05

1 Answers1

5

.await is a keyword, thus asking how it is defined is equivalent to asking how if or match are defined. The true answer is an unsatisfying one: look at the code of the Rust compiler.

The async/await RFC describes it as:

The await! compiler built-in

A builtin called await! is added to the compiler. await! can be used to "pause" the computation of the future, yielding control back to the caller. await! takes any expression which implements IntoFuture, and evaluates to a value of the item type that that future has.

// future: impl Future<Output = usize>
let n = await!(future);

The expansion of await repeatedly calls poll on the future it receives, yielding control of the function when it returns Poll::Pending and eventually evaluating to the item value when it returns Poll::Ready.

await! can only be used inside of an async function, closure, or block. Using it outside of that context is an error.

(await! is a compiler built-in to leave space for deciding its exact syntax later. See more information in the unresolved questions section.)

The expansion of await

The await! builtin expands roughly to this:

let mut future = IntoFuture::into_future($expression);
let mut pin = unsafe { Pin::new_unchecked(&mut future) };
loop {
    match Future::poll(Pin::borrow(&mut pin), &mut ctx) {
          Poll::Ready(item) => break item,
          Poll::Pending     => yield,
    }
}

This is not a literal expansion, because the yield concept cannot be expressed in the surface syntax within async functions. This is why await! is a compiler builtin instead of an actual macro.

Note that the syntax await!(expression) was changed to expression.await before stabilization.

Does the compiler have magical knowledge of the Future trait

Yes. Future is a language item.

can x be anything that implements IntoFuture or is it more complicated?

There isn't actually an IntoFuture in the standard library; I assume that the level of indirection wasn't needed during implementation. .await only works with types implementing Future.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thank you for the detailed answer. There is just one piece of the puzzle remaining for me: the validity of the `.await` keyword used on an object `x`. Does the compiler have magical knowledge of the `Future` trait and can `x` be anything that implements `IntoFuture` or is it more complicated? Also, since you seem quite knowledgeable about the compiler, would you be so kind to point me to where I should look in the compiler code? – MrMobster Dec 13 '19 at 16:53