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.
| Operation | Result |
|---|---|
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.