---
title: "23.2 async/await and Futures"
description: "How async functions produce lazy Futures, what .await does, and why you need a runtime to make any of it actually run."
url: "https://learnrust.net/chapter-23/async-await-and-futures/"
last_updated: "2026-06-13"
---

# 23.2 async/await and Futures

You know *when* to use async (lesson [23.1](https://learnrust.net/chapter-23/what-async-is/)): many IO-bound waits at once. Now the syntax. Rust adds two keywords, `async` and `await`, and one underlying type, the `Future`. The single most important thing to understand, and the thing that trips up everyone arriving from other languages, is that an async function in Rust does *nothing* when you call it. It hands you a recipe, not a result, and the recipe only runs when something drives it. Once that clicks, the rest follows.

## async makes a function return a Future

Put `async` in front of a function, and its return type quietly changes. This function looks like it returns a `u32`:

```rust
async fn compute() -> u32 {
    42
}
```

But calling `compute()` does *not* give you `42`, and it doesn't run the body. It gives you a **`Future`**, a value representing a computation that *will* produce a `u32` when driven to completion. A `Future` is lazy in exactly the way an iterator is lazy (chapter 19): building it does nothing; you have to consume it. Watch the difference:

```rust
async fn compute() -> u32 {
    println!("computing!");
    42
}

fn main() {
    let fut = compute();        // body does NOT run; no "computing!" printed
    println!("made a future, ran nothing");
}
```

```
made a future, ran nothing
```

`compute()` returned a `Future` and `fut` holds it, but "computing!" never prints, because the body never ran. The function call built the recipe and stopped. This is the chapter-19 laziness lesson again: just as a `map` adapter does nothing until consumed, an async function does nothing until its `Future` is driven. If you come from JavaScript or Python, where calling an async function *starts* it, this is the surprise to unlearn: in Rust, futures are inert until polled.

## .await drives a Future, but only inside async

So how do you get the `42` out? You **`.await`** the future. `.await` says "run this future to completion and give me its value, and while it's waiting, let other tasks use this thread." But there's a catch with teeth: you can only use `.await` *inside an `async` function*. An async function awaiting another async function is the normal shape:

```rust
async fn compute() -> u32 {
    42
}

async fn run() {
    let n = compute().await;   // drive the future, get the u32
    println!("got {n}");
}
```

Inside `run` (which is `async`), `compute().await` runs the future and binds `42` to `n`. The `.await` is the point where, if `compute` had to wait for something, `run`'s task would step aside and let other tasks proceed (lesson [23.1](https://learnrust.net/chapter-23/what-async-is/)). Awaiting is both "get the result" and "here's a place I'm willing to yield."

But notice the problem this creates. `.await` only works inside `async`. So `run` must be `async` to await `compute`. And whoever calls `run` must `.await` *it*, so they must be `async` too. The async-ness propagates outward, function by function, all the way up. This is the famous **function coloring**: async functions can only be awaited by other async functions, so "async" spreads through a call chain like a dye. It's one of the real costs the last lesson warned about, and it's why making one function async tends to pull its callers along.

## The chain has to stop somewhere: main can't await

Follow the coloring up and you hit a wall. `main` is the top, and `main` is not `async`, you can't write `let n = run().await` in an ordinary `main`, because `main` isn't an async function and `.await` is forbidden outside one. So how does the *first* future ever get driven? Something at the very top must take a future and run it to completion through plain, non-async means. That something is a **runtime**, and Rust, deliberately, does not ship one.

```rust
async fn run() {
    println!("running!");
}

fn main() {
    let fut = run();
    // fut is a Future. Nothing drives it. "running!" never prints.
    // We can't .await here: main isn't async.
}
```

This compiles, prints nothing, and even warns that the future is unused, the laziness biting one more time. To actually run `run()`, you need an engine that accepts a future and polls it to completion, parking and waking it as its waits resolve. That engine is the **async runtime**, the subject of the next lesson.

> **Why Rust ships no runtime, and what that means for you**
>
> Most languages bake one async runtime into the standard library. Rust deliberately doesn't: it standardizes the `async`/`await` *syntax* and the `Future` *trait*, but leaves the actual executor, the thing that runs futures, to libraries, so different domains (network servers, embedded devices, GUIs) can pick a runtime suited to them. The upside is flexibility; the very visible downside is that *you must add a runtime dependency to run a single line of async code*. There's no built-in `block_on`. This is a real seam, and a frequent source of the beginner question "why won't my async code run?" The answer is almost always "you haven't given it a runtime yet."

## Futures are a trait, briefly

Under the syntax, a `Future` is a trait with one method, `poll`, that the runtime calls repeatedly: each call either returns `Ready(value)` (done) or `Pending` (not yet, try again when I signal). You will essentially never call `poll` or implement `Future` by hand, the `async`/`await` syntax generates all of it for you, the same way `for` generates `next` calls (chapter 19). It's worth knowing the trait is there, because it explains the model: the runtime drives your futures by polling them, and `.await` is sugar for "poll this until `Ready`, yielding on `Pending`." But day to day, you write `async fn` and `.await`, and the machinery stays out of sight.

## Quiz time

**Question #1**

What does calling an `async fn` actually do, and what does it *not* do?

<details class="solution">
<summary>Show solution</summary>

Calling an `async fn` builds and returns a `Future` (a lazy value representing the eventual result). It does *not* run the function body or produce the result, no code in the body executes yet. A `Future` is inert until something drives it (via `.await` or a runtime), exactly like an iterator adapter is lazy until consumed. This surprises people coming from languages where calling an async function starts it running.

</details>

**Question #2**

What does `.await` do, and why can you only use it inside an `async` function?

<details class="solution">
<summary>Show solution</summary>

`.await` drives a future to completion and yields its value, and at that point it allows the task to step aside (yield the thread) while waiting. It's only allowed inside `async` functions because awaiting is the mechanism by which an async function suspends and resumes; a normal function has no such suspension machinery. This is why async "colors" functions: to await something, the caller must itself be async, so async-ness propagates up the call chain.

</details>

**Question #3**

Why does async code "not run" if you just call an async function from `main`, and what's missing?

<details class="solution">
<summary>Show solution</summary>

Calling the async function only creates a `Future`, which is lazy and does nothing until driven. You can't `.await` it in `main` because `main` isn't async. What's missing is a *runtime* (executor): the engine that takes a future and polls it to completion through non-async means. Rust deliberately ships no built-in runtime, so you must add one (a dependency like Tokio) before any async code will actually execute.

</details>

You have the syntax and you've hit the wall: nothing runs without a runtime. The next lesson ([23.3](https://learnrust.net/chapter-23/using-tokio/)) brings in Tokio, by far the most common async runtime, and finally makes async code execute, with a small concurrent fetcher to show what it's for.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
