Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.io.typed

Typed byte streams preserve a caller-selected error payload without side storage. They complement std.io; existing streams keep their APIs.

import std.bytes
import std.io
import std.io.typed as typed
from std.io.typed import Partial, ReadFailure

def main() -> Result[(), IoError | AllocError | std.bytes.BytesError | Partial[ReadFailure[std.io.StreamError]]]:
    mut source = typed.reader(std.io.cursor(std.bytes.from_text("header:body")?))
    mut buffer = std.bytes.with_capacity(6)?
    buffer.resize(6, 0u8)?
    typed.read_exact(&mut source, &mut buffer, 0, 6)?
    print(buffer.to_text()?)?
    match typed.read_exact(&mut source, &mut buffer, 0, 6):
        case Ok(_):
            print("complete")?
        case Err(partial):
            print(partial.completed, partial.cause)?
    Ok(())
header
5 std.io.typed.ReadFailure[std.io.StreamError].UnexpectedEof

Reader[E].read(&mut self, &mut Bytes, start, stop) and Writer[E].write(&mut self, &[u8]) return Result[i64, StepError[E]]. The read range is half-open and must satisfy 0 <= start <= stop <= buffer.len(); an invalid range traps with read: start/stop outside buffer before consulting the transport. read_exact traps with read_exact: start/stop outside buffer. A writer takes the bytes as a view. A successful call reports the bytes moved; a failed call reports no progress from that attempt. StepError.Interrupted requests retry. Source(E) preserves every other cause, including cancellation and allocation failure. Reader Ok(0) is EOF for a nonempty range. Writers need only write, not flush.

OperationResult
read_exact(reader, buffer, start, stop)Result[(), Partial[ReadFailure[E]]]
write_all(writer, buffer: &[u8])Result[(), Partial[WriteFailure[E]]]
copy_all(reader, writer, scratch)Result[i64, CopyFailure[E, G]]

Helpers retry only Interrupted. Partial carries completed: i64 and cause. ReadFailure[E] distinguishes Source(E), UnexpectedEof, InvalidProgress, and OutOfRange; WriteFailure[E] has Source(E), WriteZero and InvalidProgress. Negative or oversized counts are invalid progress. Invalid read ranges and empty reads or writes do not call the underlying stream.

CopyFailure.Read(completed, ReadFailure[E]) and CopyFailure.Write(completed, WriteFailure[G]) preserve the failing operation and independent source/destination error types. Counts mean bytes delivered to the writer, including a short write before failure. A read chunk may already have been consumed when writing fails: this API does not promise resumable copying or retain unwritten bytes. Empty scratch is an OutOfRange read failure. A chunk that would make the delivered total exceed i64.MAX is Read(completed, ReadFailure.OutOfRange) before writing that chunk; it is not an invalid count from the transport. Internal cursor updates use documented bounds-proven opt-outs, without changing the stream’s declared error type. Helpers allocate nothing and require caller-provided storage.

buffered_reader(inner, capacity: Positive[i64]) returns Result[BufReader[E, R], StreamError] for a Reader[E]; construction allocates the buffer, which is its only failure. Its read returns StepError[ReadFailure[E]], preserving a transport cause as Source(ReadFailure.Source(E)) and forwarding Interrupted. Invalid ranges and empty reads do not call the transport. Invalid inner counts become ReadFailure.InvalidProgress. Reads allocate nothing; capacity() and buffered() report the buffer size and unread bytes.

The public inner field permits transport-specific operations, but consuming it discards prefetched bytes. Keep the whole buffered reader when handing a connection from head parsing to body decoding or to the next HTTP message.

reader(inner) wraps an existing std.io.Reader as LegacyReader[R]; writer(inner) wraps a std.io.Writer as LegacyWriter[W]. Their typed cause is StreamError. Only its Interrupted variant becomes a retry signal; other variants survive intact. unwrap_reader(bridge) and unwrap_writer(bridge) consume the bridge and return the owned stream, allowing explicit flushing, seeking or closing. Copying does not flush or close. These bridges cannot recover causes already erased by a legacy adapter: For HTTP client responses, prefer std.http.client.typed_response(response) over bridging Response.read: its typed cause is the complete ClientError. The legacy Response.read/take_failure pair remains a compatibility API.

See typed HTTP framing and the tutorial.