Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions component-model/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- [Interfaces](./design/interfaces.md)
- [Worlds](./design/worlds.md)
- [Packages](./design/packages.md)
- [Async, Streams, and Futures](./design/async.md)
- [WIT By Example](./design/wit-example.md)
- [WIT Reference](./design/wit.md)
- [Migrating from WASI P2 to WASI P3](./design/migrating-to-p3.md)

# Using WebAssembly Components

Expand Down
65 changes: 65 additions & 0 deletions component-model/src/design/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Async, Streams, and Futures

WASI P3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream<T>`, and `future<T>`. Together, they let interfaces express asynchronous operations that compose across component boundaries.

For migration mechanics (e.g., how a WASI P2 component maps onto these primitives) see [Migrating from WASI P2 to WASI P3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves.

## Native async

The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI P3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them.

That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one.

Native primitives close the gap. With `async func`, `stream<T>`, and `future<T>` in the Canonical ABI, scheduling and wake-up propagation become the runtime's job rather than any individual component's. Components can pass futures and streams along the chain without keeping their own event loops running to relay readiness.

## The three primitives

### `async func`

A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop.

```wit
handle: async func(request: request) -> result<response, error-code>;
```

Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python.

### `stream<T>`

A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream<T>` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups.

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

### `future<T>`

A typed handle for a single value that will become available later. Like `stream<T>`, `future<T>` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future<T>` does not block; the caller awaits the result when it needs it.

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## How the primitives work in WASI P3

### Stream plus terminal future

Reads return both a data channel and a completion handle, packed into a tuple:

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

The two halves are independent. The caller can consume the stream eagerly, sample it, or drop it part-way through; either way the future resolves once the operation has terminated, carrying the success-or-failure outcome. The same shape appears in stdin, filesystem reads, TCP receives, and directory listings.

### Stream parameter, future return

Writes use the symmetric shape: the guest supplies the data as a `stream<u8>` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape:

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## Where to go next

For an end-to-end Rust example that uses these primitives in practice, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For runtime support and CLI flags, see [Wasmtime](../running-components/wasmtime.md). For the WIT syntax in detail, see [WIT Reference](./wit.md).
4 changes: 4 additions & 0 deletions component-model/src/design/component-model-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ For example, the [wasi-http](https://github.com/WebAssembly/WASI/blob/main/propo
an `imports` world encapsulating the interfaces that an HTTP proxy depends on,
and a `proxy` world that depends on `imports`.

### Async, Streams, and Futures

The Component Model includes [`async func`, `stream<T>`, and `future<T>`](./async.md) as native Canonical ABI primitives, introduced alongside WASI P3. Together, they let interfaces express asynchronous operations that compose across component boundaries.

### Platforms

In the context of WebAssembly, a _host_ refers to a WebAssembly runtime
Expand Down
107 changes: 107 additions & 0 deletions component-model/src/design/migrating-to-p3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Migrating from WASI P2 to WASI P3

WASI P3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream<T>`, and `future<T>`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives.

This page covers the mapping between concepts in WASI P2 and WASI P3. For a WIT-level comparison of every WASI P3 interface, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev.

## Do you need to migrate?

Not immediately. WASI P3 runtimes can polyfill P2 by mapping P2 imports onto native P3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both P3 and P2 components from the same binary, dispatching per component. Migration is the right call when you want:

- Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away).
- The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two.
- First-class support in P3-targeted toolchains as they continue to land.

## Concept mapping

WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one:

| WASI P2 (`wasi:io`) | WASI P3 (Component Model) |
| -------------------------------- | ---------------------------------------- |
| `resource pollable` | `future<T>` |
| `resource input-stream` | `stream<u8>` |
| `resource output-stream` | `stream<u8>` (passed *into* the call) |
| `poll(list<pollable>)` | `await` on a future |
| `subscribe()` on a resource | return a `future` from the call |
| `start-foo` / `finish-foo` | a single `func` or `async func` |

## What changed in WIT

### Stream-plus-future for reads

A P2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. P3 splits those concerns: the call returns a `stream<u8>` for the data and a `future<result<_, error-code>>` for the outcome, packed into a tuple.

```wit
// WASI P2 (filesystem read)
read-via-stream: func(offset: filesize) -> result<input-stream, error-code>;

// WASI P3 (filesystem read)
read-via-stream: func(offset: filesize) -> tuple<stream<u8>, future<result<_, error-code>>>;
```

In P3 the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way.

### Write-direction flip

P2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. P3 inverts that: the guest supplies the data as a `stream<u8>` value, and the host returns a `future` that resolves once it has finished consuming the stream.

```wit
// WASI P2: receive an output-stream resource, write into it
get-stdout: func() -> output-stream;

// WASI P3: pass a stream value in, receive a completion future
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

### Two-step calls collapsed

P2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. P3 collapses each pair into a single call:

```wit
// WASI P2
start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;

// WASI P3
connect: async func(remote-address: ip-socket-address) -> result<_, error-code>;
```

The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`).

## Interface highlights

The complete per-interface diff lives on [WASI P3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are:

- **`wasi:io` is gone.** The package has no 0.3.0 release. Every resource it exposed (`pollable`, `input-stream`, `output-stream`) is replaced by a Component Model primitive, per the [concept mapping](#concept-mapping) above.
- **`wasi:http` collapses from nine resources to two.** The incoming/outgoing × request/response/body matrix plus `future-trailers`, `future-incoming-response`, and `response-outparam` all become `request` and `response`, with `stream<u8>` bodies and a `future` for trailers. The handler is now an `async func`:

```wit
// WASI P2
handle: func(request: incoming-request, response-out: response-outparam);

// WASI P3
handle: async func(request: request) -> result<response, error-code>;
```

The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler.
- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven P2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream<tcp-socket>` directly instead of requiring a separate `accept` loop.

Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above.

## Tooling requirements

| Tool | Minimum | Notes |
| ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
| Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. |
| `wit-bindgen` | 0.46+ | Use the `async` feature for P3 binding generation. |
| jco | latest | P3 host bindings ship in the `preview3-shim` package. |
| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. |
| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for P3 outputs of `wit-bindgen` 0.58. |

> **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh.

## Further reading

- [Async, Streams, and Futures](./async.md) — the conceptual foundation
- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the P3 `async fn run()` pattern
- [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface
Loading