hanki

stream

stdlib/core/stream.hk: the Stream trait, a byte transport that can be read, written, and closed.

It exists so framing code can be written once and run over more than one transport. extra/http's request/response framing is the motivating caller: it takes S: Stream in place of a TcpStream, and the same functions serve a TLS connection once one exists.

The error type is sys.NetError and no per-transport one. A shared Net | Tls sum was the alternative: it would name a TLS concept in core before any TLS code exists, and adding a variant later breaks every exhaustive match over it. A transport maps its own failures into NetError, and the distinction is restored one level up where callers match: http.HttpError already has Net(...) and gains a TLS variant beside it when client TLS lands.

close! returns () and no Result, that being what the transport does - sys.socket_close! cannot fail. Declaring it fallible here would invent an error TcpStream never produces and make every caller handle it.

The error type is provisional, which matters because §17 is a locked surface. The intended shape is an associated type Error, which changes read! and write! for every implementor. A bound on a trait declaring an associated type is legal now. What remains before the migration is a conversion trait for the error to travel through, and a bound on the S.Error projection to name it. No other part of the trait is expected to move.

The row is [net] on every method, and this therefore generalises over the transport type and not over the effect: an in-memory implementor still forces [net] on its callers, and framing it inside an [io]-only action is H0601. So this does not make the framing callable from the pure test pack, and a program that only frames bytes in memory is still refused under --deny net. Effect-polymorphic rows are what would lift it; tracked separately.

No hermetic test block: every implementation bottoms out in a real OS socket, with no pure result to assert. The framing built on this trait is unit-tested over in-memory buffers in extra/http.

Stream

trait Stream

read!

def read!(self, max: u32) -> Result<bytes, sys.NetError> [net]

Read once, returning up to max bytes. An empty bytes is EOF (the peer closed). Err on an I/O error or a closed transport. @no-doctest: reads a real transport; needs a peer, cannot assert in a doctest

write!

def write!(self, data: bytes) -> Result<i64, sys.NetError> [net]

Write once, returning the count written, which may be less than data.length. Writing the whole buffer is the caller's loop. @no-doctest: writes a real transport; needs a peer, cannot assert in a doctest

close!

def close!(self) -> () [net]

Release the transport. An implementor must make this idempotent, and must also release on drop; reclamation is per-actor reference counting (HANKI.md §4), and a TcpStream therefore closes when its last handle goes and an explicit close! is the eager form. Nothing enforces either for a transport defined outside this module; both are the contract. @no-doctest: side-effecting close; no return value to assert