Skip to content

std.async.io

std.async.io defines async byte-oriented interfaces and exact-fill/read helpers. It shares std.io.IOError so sync and async stream code report the same error cases. Its reader and writer contracts mirror std.io; concrete nominal resources such as net.TcpStream provide the methods as cold async.Future values.

RFC 0065 removes the public integer-descriptor adapters and the async.io.stdin/stdout/stderr experiment. A safe portable API never constructs an I/O resource from usize, returns its raw identity, or changes process standard-handle blocking mode as a hidden side effect. A future resource-specific async standard-stream API requires a separate contract.

How It Works

async.io.Reader reads bytes into caller-provided storage and returns the number of bytes read as async.Future<usize throws(io.IOError)>. A return of 0 means end-of-stream for stream-shaped handles. async.io.Writer writes all supplied bytes or returns an error, and async.io.Stream combines both capabilities.

Zynx
interface Reader {
	fn read(&self, buffer: &[u8]) -> ^async.Future<usize throws(io.IOError)>;
	fn read<region L>(&self in L, output: ^OutputSpan<u8>)
		-> ^async.Future<^OutputSpan<u8> throws(io.IOError)> in L;
}

interface Writer {
	fn write(&self, data: bytes.Bytes) -> ^async.Future<void throws(io.IOError)>;
}

interface Stream: Reader, Writer;

async.io.Reader.read(buffer) is one async read operation. It may complete with fewer bytes than requested, and 0 from a non-empty buffer means EOF. async.io.read is the exact-fill helper: it loops until the whole buffer is filled, or throws io.IOError.UnexpectedEof after EOF with any bytes already copied left in place.

The read(^OutputSpan<u8>) overload initializes new bytes instead of overwriting existing ones. It consumes the session because the future, runtime, or kernel may retain its pointer after returning Pending. Success returns the same session after advancing exactly the initialized prefix; the caller then continues writing, commits it, or cancels it explicitly:

Zynx
// RFC 0054 target; implementation pending.
var output = try (<-bytes).output(4096);
output = try await (&reader).read(<-output);
bytes = (<-output).commit();

An I/O error drops the consumed session and therefore the entire owned collection. It does not synthesize a rollback owner. A request to cancel an in-flight operation is not completion: the future must keep owning the session until the kernel can no longer access its pointer. A borrowed asynchronous output form is valid only for a poll-only implementation whose Pending path performs no write and retains no pointer; it is not the retained-resource I/O contract.

async.io.Writer.write(data) is a complete-write operation. It writes every byte or throws. A resource-backed writer may internally loop over partial host writes and wait for readiness, but neither a raw handle nor the readiness wait is public API.

Async initialized-overwrite I/O stores borrowed buffers inside the returned async. A destination slice passed to read, or a bytes.Bytes view passed to write, must remain alive and must not be mutated in a way that changes or reuses its backing storage until the future has completed. Use owned storage with a lifetime that covers the await; do not build a future from a temporary byte view and then allow the source storage to go out of scope before awaiting it.

Initialization-aware output does not borrow spare capacity. Its overload moves the OutputSpan<u8> owner into the future and returns that owner only on success.

Byte Helpers

async.io.write writes a borrowed bytes.Bytes view, and async.io.read(reader, buffer) fills the requested buffer exactly or returns IOError.UnexpectedEof. Larger operations such as copying streams or reading until end-of-stream are explicit loops built from readers and writers.

TCP illustrates the generic contract without exposing its host identity:

Zynx
// RFC 0065/0066 target; Network enters through a private root @host loan.
var output = try (<-storage).output(4096);
output = try await stream.read(<-output);
storage = (<-output).commit();

try await stream.write(bytes.Bytes("hello\n"));

Ownership, Allocation, Errors, And Unspecified Behavior

std.async.io helpers do not allocate stream buffers for callers. Initialized read and write futures retain borrowed slices or byte views until completion, so the backing storage must outlive the future. An output future instead owns its OutputSpan<u8> until true completion. Operation failures use std.io.IOError; raw platform error codes are not preserved.

Runtime readiness timing, fairness, poll batching, and the exact number of host notifications observed by a future are unspecified. Readiness registration is private and is tied to a live nominal resource plus its generation. Use std.io for synchronous byte streams, std.async.fs for cooperative-safe offset-based files, and std.net for owned async sockets. async.fs.File does not directly implement these cursor-shaped interfaces. Raw OS interop is deferred to target-specific resource APIs rather than represented by portable integers.

API Reference

Re-exports

NameSourceDescription
IOErrorstd.io.IOErrorShared I/O error cases used by sync and async stream helpers.

Interfaces And Types

TypeKindPublic fieldsMethodsDescription
ReaderinterfaceN/AreadAsync byte reader capability.
WriterinterfaceN/AwriteAsync byte writer capability.
StreaminterfaceN/Aread, writeAsync ordered byte stream capability.

Functions

NameSignatureParametersReturnsDescription
writewrite<W: Writer>(writer: &W, data: bytes.Bytes) -> ^async.Future<void throws(io.IOError)>writer: async writer, data: bytes to write^async.Future<void throws(io.IOError)>Writes all bytes asynchronously or returns an error.
readread<R: Reader>(reader: &R, buffer: &[u8]) -> ^async.Future<void throws(io.IOError)>reader: async reader, buffer: mutable destination bytes^async.Future<void throws(io.IOError)>Fills the entire buffer asynchronously or returns IOError.UnexpectedEof.

Interface Methods

MethodSignatureParametersReturnsDescription
Reader.readread(&self, buffer: &[u8]) -> ^async.Future<usize throws(io.IOError)>buffer: mutable destination bytes^async.Future<usize throws(io.IOError)>Reads bytes asynchronously and returns the number of bytes read.
Reader.readread<region L>(&self in L, output: ^OutputSpan<u8>) -> ^async.Future<^OutputSpan<u8> throws(io.IOError)> in Loutput: owned bounded uninitialized byte suffix^async.Future<^OutputSpan<u8> throws(io.IOError)> in LRetains the output owner until true completion and returns it after advancing exactly the initialized prefix.
Writer.writewrite(&self, data: bytes.Bytes) -> ^async.Future<void throws(io.IOError)>data: bytes to write^async.Future<void throws(io.IOError)>Writes bytes asynchronously or returns an error.

Status

std.async.io is the accepted generic async byte-stream API. RFC 0054's owning output overload and RFC 0065's descriptor-free boundary are not yet fully implemented. For scheduler and future semantics, see std.async. For the full exported surface, see Standard Library Inventory.

Released under the MIT License.