Asynchronous Rust

.await

The .await postfix operator (expr.await, usable inside async fn and async blocks) drives a Future (or any type implementing std::future::IntoFuture) to completion by polling it, yielding Poll::Pending back to the caller's executor whenever the inner future is not yet ready, and evaluating to Future::Output once Poll::Ready(val) is returned.

Why .await Uses Postfix Syntax (fut.await?...)

Rust designed .await as a postfix field-like operator rather than a prefix keyword (await fut) so it chains naturally left-to-right with method calls and the ? error-propagation operator without parentheses:

Prefix Syntax in Other Languages (Requires Nested Parentheses):
  let text = await (await (await client.get(url)).send()).text();

Rust Postfix `.await` + `?` Chaining (Clean Left-to-Right Pipeline):
  let text = client
      .get(url)
      .send()
      .await?
      .text()
      .await?;

How expr.await Desugars Under the Hood

When you write sub_future.await inside an async block, the compiler lowers it to a loop that converts sub_future via IntoFuture::into_future, pins it as a state-machine field, and polls it using the caller's Context<'_>:

Conceptual Desugaring of `let result = expr.await;`:

  let mut pinned_sub = IntoFuture::into_future(expr);
  loop {
      match Future::poll(Pin::new_unchecked(&mut pinned_sub), cx) {
          Poll::Ready(val) => break val,
          Poll::Pending => {
              // Save current state-machine variant and return `Poll::Pending`
              // up to the executor! When woken, resume at the top of this loop!
              yield Poll::Pending;
          }
      }
  }

Custom Types That Can Be .awaited via std::future::IntoFuture

Stabilized in Rust 1.64, .await does not require its operand to implement Future directly—it requires std::future::IntoFuture (which has a blanket implementation for all F: Future!).

Implementing IntoFuture on a request builder struct allows users to configure the builder and then .await the builder directly without calling an extra .send() or .build() method first:

use std::future::{Future, IntoFuture, Ready, ready};
use std::pin::pin;
use std::task::{Context, Poll, Waker};

struct QueryBuilder {
    table: &'static str,
    limit: usize,
}

impl QueryBuilder {
    fn new(table: &'static str) -> Self {
        Self { table, limit: 10 }
    }

    fn limit(mut self, limit: usize) -> Self {
        self.limit = limit;
        self
    }
}

// Implementing `IntoFuture` allows `.await`ing `QueryBuilder` directly!
impl IntoFuture for QueryBuilder {
    type Output = String;
    type IntoFuture = Ready<String>;

    fn into_future(self) -> Self::IntoFuture {
        ready(format!("SELECT * FROM {} LIMIT {}", self.table, self.limit))
    }
}

async fn run_query() -> String {
    // `.await` invokes `<QueryBuilder as IntoFuture>::into_future` automatically:
    QueryBuilder::new("orders").limit(25).await
}

fn main() {
    let mut fut = pin!(run_query());
    let mut cx = Context::from_waker(Waker::noop());
    if let Poll::Ready(sql) = fut.as_mut().poll(&mut cx) {
        println!("executed via IntoFuture: {}", sql);
    }
}
executed via IntoFuture: SELECT * FROM orders LIMIT 25