Module sys/future

sys/future
Stability: unstable — may still change; see below.

IoFuture — the opaque handle every async I/O extern hands back.

This is the one type the whole std/sys async surface is written in terms of: sys/file, sys/dir, sys/tcp, sys/udp, sys/unix, sys/dns, sys/process and sys/timer all return it, and io.await is what turns it back into an i32. Every public module above them (std/fs, std/net, std/process, std/time/sleep) reaches it only through those.

Stability

unstable — this is an ABI, not an API. __yo_io_future_t is emitted by the compiler's own runtime (src/codegen/async/) with a layout the await codegen indexes directly (state, result), so its shape is pinned to the async state-machine representation and moves with it, not with a library decision. Its value convention is one thing on every platform since 2026-10-03: a resolved i32 that is a non-negative syscall result or a negative errno, with sys/dns alone resolving to its documented DNS_ERR_* codes (above every errno). The raw i32 stays — a typed Result(i32, IoError) channel at the extern boundary was considered and declined (plans/ASYNC_IO_API_AUDIT.md Q2): IoError.check and IoError.from_result turn the answer into a value at the call site. What would still have to be true to freeze it: a state-machine layout that codegen promises to keep.

Types

IoFuture type-alias
Impl(Future(i32))

A pending (or already finished) I/O operation, resolved with io.await.

Impl(Concrete(__yo_io_future_t), Future(i32)): an opaque C struct laid out like an async state machine, so await reads its state and result the same way it reads a Yo async block's. state 0 means still in flight, -1 already completed (the common case on macOS and Windows, where most file operations run synchronously and are handed back finished) and -2 aborted — see the effects model in AGENTS.md.

It resolves to a raw i32: a non-negative syscall result (an fd, a byte count, or 0) or a NEGATIVE errno, on every platform (the Windows runtime maps Winsock's codes to errno). Nothing here throws, so error handling is IoError.from_result / IoError.check at the call site. sys/dns resolves to its own DNS_ERR_* codes, which std/net/dns maps to DnsError. io.state reads an in-flight raw future as Running: it was submitted when it was created, so it is never Cold.