# Rust Syntax Async Await

> Use when the user writes `async fn`, awaits a future, uses AFIT (1.75) / RPITIT (1.75) / async closures (1.85), holds non-Send state across `.await`, needs the Pin / Unpin semantics, or hits Future-not-Send compile errors. Prevents holding MutexGuard or Rc across `.await`, calling `.poll` directly, confusing async fn with parallel execution, forgetting pinning when implementing Future manually, and edition-2024 RPIT precise-capturing surprises. Covers: `async fn` desugaring, `.await` suspension, async blocks, AFIT (1.75), RPITIT (1.75), async closures (1.85), Future contract (poll idempotency, waker invariant), Pin / Unpin (deep in references/pinning.md), cancellation via drop, holding non-Send across `.await`, edition 2024 RPIT lifetime capture interaction. Keywords: async fn, ".await", "async block", AFIT, RPITIT, "async trait", "async closure", "async ||", Future, "Future not Send", Pin, Unpin, "self-referential", "pinning projection", "structural pinning", "MutexGuard across await", "Send across await", "

- Skill: `impertio-studio/rust-syntax-async-await` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/rust-syntax-async-await`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/rust-syntax-async-await/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/rust-syntax-async-await

---


# rust-syntax-async-await

The **mechanics** of `async fn`, `.await`, and the `Future` trait in Rust: how `async fn` desugars to a state machine returning `impl Future`, what `.await` actually does at a suspension point, how AFIT (1.75), RPITIT (1.75), and async closures (1.85) compose with the trait system, why `Pin` exists, and what kinds of state may or may not be held across an `.await`.

This skill is **executor-agnostic**: it covers the language and the trait contract. For runtime specifics (`#[tokio::main]`, `spawn`, `JoinSet`, `select!`) see [[rust-impl-async-tokio]]. For runtime-architecture concepts (poll-vs-push, reactor, structured concurrency) see [[rust-core-async-runtime]]. For async error patterns (`?` in `async fn`, `JoinError`, cancellation safety) see [[rust-errors-async]]. For `Pin` / `Rc` / `Arc` / `Box` ownership see [[rust-syntax-smart-pointers]].

Deep `Pin` / `Unpin` mechanics live in [references/pinning.md](references/pinning.md). Complete API surface lives in [references/methods.md](references/methods.md). Worked examples live in [references/examples.md](references/examples.md). Anti-patterns live in [references/anti-patterns.md](references/anti-patterns.md).

---

## When to use this skill

- User writes `async fn foo() -> T { ... }` and needs to know what it returns
- User calls `fut.await` and asks what `.await` does
- User defines an async function in a trait (AFIT, 1.75) or returns `impl Trait` from a trait method (RPITIT, 1.75)
- User writes an async closure `async || { ... }` (1.85) or hits the `AsyncFn` / `AsyncFnMut` / `AsyncFnOnce` traits
- Compiler says "future cannot be sent between threads safely" or "future is not `Send`"
- User wants to hold a `MutexGuard`, `RefCell` borrow, `Rc`, or any `!Send` value across `.await`
- User implements `Future` by hand and asks about `Pin<&mut Self>` in `poll`
- User asks how to cancel a future (answer: drop it)
- User hits "cannot move out of pinned" or wants to use `pin!` / `Box::pin`
- User asks why their async fn signature requires `+ use<>` in edition 2024

For `tokio::spawn`, `JoinSet`, `select!`, channels, timers and concrete runtime setup see [[rust-impl-async-tokio]].

---

## Quick reference table

| Concept | Syntax | Compiler semantics |
|---------|--------|--------------------|
| Async function | `async fn f() -> T` | Returns `impl Future<Output = T>`; body is a state machine |
| Async block | `async { expr }` | Same as `async fn` but anonymous; captures by reference unless `async move` |
| Async closure (1.85) | `async \|x\| { ... }` | Implements `AsyncFn` / `AsyncFnMut` / `AsyncFnOnce` |
| Await | `fut.await` | Yields control to the executor; resumes when `Future::poll` returns `Ready` |
| AFIT (1.75) | `trait T { async fn m(&self); }` | Desugars to `fn m(&self) -> impl Future + use<'_, Self>` |
| RPITIT (1.75) | `trait T { fn m(&self) -> impl Future<...>; }` | Anonymous return type captured by the impl |
| Manual `Future` | `impl Future for S { fn poll(self: Pin<&mut Self>, cx) -> Poll<_> }` | Must be `Pin`ned to be polled |
| `pin!` macro (1.68) | `let mut fut = pin!(some_future);` | Stack-pin a future without `Box::pin` |
| Cancellation | `drop(fut)` | Runs destructors of locals at the current suspension point |
| Edition 2024 RPIT | `fn f<'a>() -> impl Future + use<'a>` | Default capture is "all in scope generics & lifetimes" |

---

## `async fn` desugaring

```rust
async fn fetch(url: &str) -> Result<String, std::io::Error> {
    /* body */
    Ok(String::new())
}
```

Desugars (conceptually) to:

```rust
fn fetch<'a>(url: &'a str) -> impl Future<Output = Result<String, std::io::Error>> + 'a {
    async move {
        /* body */
        Ok(String::new())
    }
}
```

Three load-bearing facts:

1. The return type is **anonymous**: it is `impl Future<Output = T>`, never a nameable type. You cannot write `fn returns_future() -> ConcreteFutureName`.
2. The body is a **compiler-generated state machine**. Every `.await` becomes a suspension point with its own state-machine variant; locals live for at most one state.
3. The state machine is **self-referential** in general: a local that borrows another local across an `.await` produces a future whose internal references point into its own storage. This is why `Future` requires `Pin<&mut Self>` in `poll`.

[Source: Async Book ch. 1, 2][async-book] [Source: std::future::Future][future]

ALWAYS treat the return type as opaque: hand it off to an executor or `.await` it. NEVER try to spell the anonymous future type; use `impl Future<Output = T>` (or `Pin<Box<dyn Future<Output = T> + Send>>` for type erasure).

---

## `.await` mechanics

`fut.await` desugars to a loop that repeatedly calls `Future::poll` until it returns `Poll::Ready`. From the standard library:

> "When using a future, you generally won't call `poll` directly, but instead `.await` the value." [Source: std::future::Future][future]

At a suspension point (`.await` that returned `Pending`), the runtime takes back control. The future will be re-polled later, but only after some `Waker` has been signalled. From the contract:

> "When a future is not ready yet, `poll` returns `Poll::Pending` and stores a clone of the `Waker` copied from the current `Context`. This `Waker` is then woken once the future can make progress." [Source: std::future::Future][future]

> "Once a future has completed (returned `Ready` from `poll`), calling its `poll` method again may panic, block forever, or cause other kinds of problems; the `Future` trait places no requirements on the effects of such a call." [Source: std::future::Future][future]

ALWAYS `.await` a future exactly once: poll-after-ready is undefined behaviour at the trait level. NEVER call `.poll()` directly; you almost certainly want `.await` or a runtime adapter (`tokio::pin!` + `poll_fn`).

ALWAYS remember that `.await` is a **suspension point**: any value alive across `.await` becomes part of the generated `Future`'s state and must be `Send` if the future will run on a multi-threaded executor.

---

## Async blocks

```rust
let fut = async {
    let n: u32 = compute().await;
    n + 1
};
```

An async block is an anonymous future, identical in semantics to the body of an `async fn`. Capture rules match closures:

- `async { ... }` borrows captured variables (like a non-`move` closure).
- `async move { ... }` takes ownership of captured variables.

ALWAYS write `async move { ... }` when the future will outlive the current scope (for instance, when handing it to `tokio::spawn`). NEVER assume `async { ... }` will compile in `spawn`: `spawn` requires `'static`, which forces `async move`.

---

## AFIT: async fn in trait (Rust 1.75)

Stabilized in 1.75 ([Rust 1.75 release notes][rust175]):

```rust
trait HttpClient {
    async fn get(&self, url: &str) -> Result<String, std::io::Error>;
}
```

Desugars to:

```rust
trait HttpClient {
    fn get(&self, url: &str) -> impl Future<Output = Result<String, std::io::Error>> + use<'_, Self>;
}
```

Constraints:

- AFIT methods are **not dyn-compatible** by themselves. `&dyn HttpClient` does not work directly because the returned `impl Future` size is not known at the call site. For `dyn` you must use `Box<dyn Future<Output = T> + Send + '_>` explicitly via the `trait-variant` crate or manual desugaring.
- The returned future captures `&self` (or `&mut self`, or `self`), so it borrows from the receiver. This makes the future `'a` where `'a` is the lifetime of the borrow.
- The `Send`-ness of the returned future is **structural**: any `!Send` value captured (notably across `.await` inside the body) makes the whole future `!Send`. There is no syntax in plain AFIT to require the future is `Send`; for that, use RPITIT explicitly with a `Send` bound.

ALWAYS use AFIT when the trait is consumed only by static dispatch (generic `T: HttpClient`). NEVER expect `&dyn AsyncTrait` to work; use the `async-trait` crate for true object safety or use the `trait-variant` crate to generate parallel `Send` / non-`Send` versions.

[Source: Rust 1.75 release notes][rust175]

---

## RPITIT: return-position `impl Trait` in trait (Rust 1.75)

Same stabilization as AFIT:

```rust
trait Service {
    fn run(&self) -> impl Future<Output = ()> + Send;
}
```

Differences from AFIT:

- You write the return type **explicitly**, so you can add bounds like `+ Send`, `+ 'static`, or `+ use<'a>`.
- Each `impl` may return a **different concrete** future type, but it must satisfy the declared bounds.
- Combine with edition 2024 precise-capture (`+ use<>` opt-out) to drop unwanted lifetime captures.

ALWAYS write `-> impl Future<Output = T> + Send` (instead of `async fn`) when callers will `tokio::spawn` the returned future. NEVER expect AFIT alone to give you `Send`: that requires RPITIT with an explicit `Send` bound.

[Source: Rust 1.75 release notes][rust175]

---

## Async closures (Rust 1.85)

Stabilized in 1.85 ([Rust 1.85 release notes][rust185]):

```rust
let id: usize = 42;
let task = async move |x: u32| -> u32 {
    do_async_thing(id).await;
    x + 1
};
let v = task(10).await;
```

Async closures implement the new `AsyncFn`, `AsyncFnMut`, `AsyncFnOnce` traits, which are the async counterparts of `Fn`, `FnMut`, `FnOnce`. Key benefit over `Fn() -> impl Future`: async closures can borrow from their environment **and** return a future that borrows from `self`, which the plain combination of `Fn` and `Future` could not express.

```rust
fn use_async_fn<F>(_f: F)
where
    F: AsyncFn(u32) -> u32,
{ /* ... */ }
```

ALWAYS prefer `async ||` when accepting an async callback that needs to borrow from its environment. NEVER reach for `Box<dyn Fn(u32) -> Pin<Box<dyn Future<...>>>>` first; on edition 2024 + Rust 1.85, `AsyncFn` is the idiomatic bound.

[Source: Rust 1.85 release notes][rust185]

---

## The `Future` trait contract

```rust
pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
```

[Source: std::future::Future][future]

Three contract clauses every implementor and every user must respect:

1. **Idempotency-after-Ready is forbidden**: once `poll` has returned `Poll::Ready(_)`, calling `poll` again on the same future is undefined behaviour at the contract level. (Implementations frequently panic; some loop forever.)
2. **Waker storage on Pending**: when `poll` returns `Poll::Pending`, the future MUST store a clone of `cx.waker()` (or assign one already-equivalent) so that the runtime can re-schedule it when progress is possible. On a second poll, the future MUST update to use the most-recent waker if it differs.
3. **`Pin<&mut Self>` is structural**: the receiver is pinned, meaning the future MUST NOT move itself or any field whose pinning is structural (see [references/pinning.md](references/pinning.md)).

ALWAYS implement `Future` only when you genuinely need a leaf future (timer, IO source). NEVER hand-implement `Future` to combine other futures; use `async { ... }` or combinators (`FuturesUnordered`, `JoinSet`, `select!`). The state-machine compiler generates correct pinning for you.

---

## Pin / Unpin overview

`Pin<P>` is a wrapper around a pointer type `P` (`Box<T>`, `&mut T`, `Arc<T>`, ...) that asserts the pointee MUST NOT be moved in memory until it is dropped. From the standard library:

> "From the moment a value is pinned by constructing a `Pin`ning pointer to it, that value must remain, valid, at that same address in memory, until its drop handler is called." [Source: std::pin][pin]

`Unpin` is a marker auto-trait. A type `T: Unpin` is freely movable even when behind `Pin`. Most types are `Unpin`. A type that needs to be pinned (self-referential state machines from `async fn`, intrusive linked-list nodes, ...) opts out with `PhantomPinned` and is `!Unpin`.

| Symbol | What it means | Typical use |
|--------|---------------|-------------|
| `Pin<&mut T>` | Pinned mutable borrow | `Future::poll` receiver, `Stream::poll_next` |
| `Pin<Box<T>>` | Pinned heap allocation | `Box::pin(fut)`, also returned by `async fn` in some forms |
| `Pin<Arc<T>>` | Pinned shared-ownership | rarely needed by hand; some lock primitives use it |
| `Unpin` (auto trait) | Safe to move even when pinned | every plain `struct`, every primitive |
| `!Unpin` | Pinned-by-design | state machines from `async`, types containing `PhantomPinned` |
| `std::pin::pin!(expr)` | Stack-pin a value | local pinning without heap allocation |

For pin projections, structural-vs-non-structural fields, `pin_project` macro, and manual `Future` impl boilerplate see [references/pinning.md](references/pinning.md).

ALWAYS take `Pin<&mut Self>` in `poll`, never `&mut self`. NEVER move out of `Pin<&mut T>` when `T: !Unpin`. NEVER call `Pin::new(&mut some_future)` on an `!Unpin` future built from `async`; that requires `unsafe` for `!Unpin` types. Use `pin!` or `Box::pin` instead.

---

## Holding state across `.await`

Every local that is alive across an `.await` becomes part of the generated future's state machine. Three consequences:

1. The state machine is **`Send`** only if every such local is `Send`.
2. The state machine is **`Sync`** only if every such local is `Sync`.
3. The state machine's size is bounded by the largest live-set across all suspension points; large locals held across `.await` blow up future size.

Common `!Send` traps:

| Holding across `.await` | Why `!Send` | Fix |
|-------------------------|-------------|-----|
| `std::sync::MutexGuard` | Guard is `!Send` (must be unlocked on the locking thread) | Drop guard before `.await`, or use `tokio::sync::Mutex` |
| `std::cell::RefMut` / `Ref` | `RefCell` is `!Sync`; guards are `!Send` | Use `tokio::sync::Mutex` or restructure to drop before `.await` |
| `Rc<T>` | `Rc` is `!Send` and `!Sync` | Use `Arc<T>` |
| Raw pointer captured from `Cell` | `Cell` is `!Sync` | Restructure or use atomic |

```rust
// BAD: future is !Send because MutexGuard is held across .await
async fn bad(mutex: std::sync::Mutex<u32>) {
    let g = mutex.lock().unwrap();   // std::sync guard
    do_io().await;                   // .await here makes future !Send
    println!("{}", *g);
}

// GOOD: scope the guard, drop before .await
async fn good(mutex: std::sync::Mutex<u32>) {
    let v = { let g = mutex.lock().unwrap(); *g };
    do_io().await;
    println!("{}", v);
}

// ALSO GOOD: async mutex from tokio (guard is Send)
async fn good_tokio(mutex: tokio::sync::Mutex<u32>) {
    let g = mutex.lock().await;
    do_io().await;
    println!("{}", *g);
}
```

ALWAYS scope a `std::sync::Mutex` lock to a block that does not contain an `.await`. NEVER hold an `Rc` across `.await` if the future will be `tokio::spawn`ed.

For more `!Send` cases and runtime-specific guidance see [[rust-errors-async]] and [[rust-impl-async-tokio]].

---

## Cancellation via drop

Async Rust has no explicit "cancel" method. Cancellation is **dropping the future**:

- A future that has not yet completed: its `Drop` impl (and the drops of every local in its current state) run. After that, the future no longer exists, so no further work happens.
- A future that is currently suspended at an `.await`: dropping it cancels at exactly that suspension point. The next `.await` is never resumed.
- A future that has already returned `Ready`: dropping the value is a normal drop.

> Implication: **anything alive across `.await` may be dropped at any suspension point**. This is structured concurrency by destructor.

ALWAYS write drop-safe code in `async` bodies: file handles, locks, in-flight network state must all behave correctly when their owning future is dropped mid-`.await`. NEVER assume code after an `.await` will run; only code before any given `.await` is guaranteed to run.

For cancellation safety in `select!` branches see [[rust-impl-async-tokio]].

---

## Edition 2024: RPIT lifetime capture

Edition 2024 ([Rust 1.85 release notes][rust185], [Edition Guide: RPIT lifetime capture][ed-rpit]) changes how `impl Trait` in return position captures generic parameters.

**Pre-2024**: `impl Trait` captures only the **type parameters** explicitly mentioned, not lifetimes.

**Edition 2024**: `impl Trait` captures **all in-scope generic parameters**, including lifetimes. To opt out of capturing a specific lifetime use `+ use<>`:

```rust
// Edition 2024: captures 'a implicitly
fn returns_future<'a>(buf: &'a [u8]) -> impl Future<Output = ()> + 'a { async { /* ... */ } }

// Same, written explicitly
fn returns_future<'a>(buf: &'a [u8]) -> impl Future<Output = ()> + use<'a> { async { /* ... */ } }

// Opt-out: do NOT capture 'a (rarely correct for async fn, breaks borrow)
fn returns_future_no_borrow<'a>() -> impl Future<Output = ()> + use<> { async { /* ... */ } }
```

Why this matters for async:

- An `async fn` desugars to an `impl Future` that **must** capture the lifetime of `&self` (and every borrowed argument), or the returned future would dangle.
- On edition 2024 the default capture is correct. On edition 2021 you sometimes had to write `async fn` workarounds or `Box<dyn Future + '_>` because RPIT did not capture lifetimes automatically.

ALWAYS default to the edition-2024 capture rule. NEVER write `+ use<>` (empty capture) on an async function whose body uses any borrowed argument; the returned future would not compile (dangling borrow).

[Source: Edition Guide: RPIT lifetime capture][ed-rpit] [Source: Rust 1.82 release notes (precise capturing)][rust182]

---

## Decision tree: which async return shape?

```
Need to return a future from a function or trait?

├─ Free function, returns a future?
│   ├─ Use `async fn` if the body is async syntax. Cleanest.
│   └─ Use `fn ... -> impl Future<Output = T> + Send + 'static`
│       when you must add bounds (Send, 'static, use<>) that `async fn` cannot.
│
├─ Trait method?
│   ├─ Generic dispatch only (T: Trait)?
│   │   ├─ AFIT: `async fn m(&self) -> T;`        (1.75)
│   │   └─ RPITIT with bounds: `fn m(&self) -> impl Future<Output = T> + Send;`  (1.75)
│   ├─ Need dyn dispatch (`Box<dyn Trait>`, `&dyn Trait`)?
│   │   ├─ Use `async-trait` crate (boxes the future), OR
│   │   └─ Hand-write `fn m(&self) -> Pin<Box<dyn Future<Output = T> + Send + '_>>`.
│
├─ Higher-order callback?
│   ├─ Rust 1.85 + edition 2024: bound by `AsyncFn(Args) -> Out`. Pass `async ||` closures.
│   └─ Pre-1.85 or compatibility: `F: Fn(Args) -> Fut, Fut: Future<Output = Out>`.
│
└─ Type-erasing a future at runtime (heterogeneous storage)?
    └─ `Pin<Box<dyn Future<Output = T> + Send>>` (alias as `BoxFuture` for ergonomics).
```

---

## Common compile errors (mini-index)

| Error | Surface symptom | Root cause | Fix reference |
|-------|-----------------|------------|---------------|
| "future cannot be sent between threads safely" | `tokio::spawn(my_fut)` fails | Some `!Send` value (MutexGuard, Rc, RefCell guard) is alive across `.await` | Scope the value or use async-aware primitive |
| "cannot move out of `Pin<&mut Self>`" | manual `Future::poll` body | Tried to deref `self.foo` of a `!Unpin` field | Use `Pin::as_mut` + projection or `pin_project` |
| "cannot use `?` operator in an async fn that returns `()`" | `?` in fire-and-forget async | Return type is `()`, no `Try` impl | Return `Result<(), E>` or use `if let Err(_) = res {}` |
| "non-defining use of `impl Future`" | trait with AFIT and `dyn` | AFIT is not dyn-compatible | Use `async-trait` or `Pin<Box<dyn Future + Send + '_>>` |
| "implementation of `Future` is not general enough" | HRTB on async fn | Future captures `'a`, caller wants `for<'a>` | Add `+ use<'a>` or restructure lifetime |
| "the trait `Send` is not implemented for `Rc<...>`" | spawn an async block that holds `Rc` | `Rc` is `!Send`; use `Arc` | Replace `Rc` with `Arc` |

For full error explanations and recovery see [[rust-errors-async]].

---

## Reference links

- [references/methods.md](references/methods.md): full API surface (`Future`, `Pin`, `pin!`, `Context`, `Waker`, `AsyncFn` family).
- [references/examples.md](references/examples.md): worked code examples for every section above.
- [references/anti-patterns.md](references/anti-patterns.md): the seven canonical async traps and their fixes.
- [references/pinning.md](references/pinning.md): the deep `Pin` / `Unpin` reference, structural projections, manual `Future` impl boilerplate, `pin_project` usage.

External:

- [The Async Book][async-book]: rust-lang.github.io/async-book
- [std::future::Future][future]: doc.rust-lang.org/std/future/trait.Future.html
- [std::pin][pin]: doc.rust-lang.org/std/pin/index.html
- [Rust 1.75 release notes][rust175]: AFIT and RPITIT
- [Rust 1.82 release notes][rust182]: precise capturing
- [Rust 1.85 release notes][rust185]: async closures + edition 2024
- [Edition Guide: RPIT lifetime capture][ed-rpit]

[future]: https://doc.rust-lang.org/std/future/trait.Future.html
[pin]: https://doc.rust-lang.org/std/pin/index.html
[async-book]: https://rust-lang.github.io/async-book/
[rust175]: https://blog.rust-lang.org/2023/12/28/Rust-1.75.0/
[rust182]: https://blog.rust-lang.org/2024/10/17/Rust-1.82.0/
[rust185]: https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/
[ed-rpit]: https://doc.rust-lang.org/edition-guide/rust-2024/rpit-lifetime-capture.html

