hanki

io

stdlib/extra/io.hk: minimal I/O surface.

A pure-Hanki face over the sys native seam: print! and read_line! hold no @intrinsic, they delegate to the raw sys stdio primitives, and the [io] effect bubbles up through the delegating call. io is an extra-tier capability like fs/process where stdout and stdin are reached the same way as the filesystem; nothing in core or the toolchain depends on this module.

No hermetic test block belongs here: every function reaches live stdio, and exercising one needs real, non-deterministic OS state that cannot be asserted in the pure stdlib test pack. Most are thin delegations to the sys native seam; read_all_bytes! is the one with logic of its own, and its loop is still only reachable through a real stream. Coverage is end-to-end through the CLI integration tests (tests/runtime/stdin_read_all.rs for the read-to-end pair) and example programs.

Line

type Line
  Line(string)
  Eof
end

read_line! returns a domain-specific sum and no string, which lets the caller distinguish "got a line" from "stream is closed". Empty input on a still-open stream is Line(""); EOF (Ctrl+D, closed pipe, etc.) is Eof. Reading past Eof is the caller's responsibility.

print!

def print!(s: string) -> () [io]

Write s to standard output verbatim; no trailing newline is added. @no-doctest: writes to stdout; side-effecting, with no return value to assert

eprint!

def eprint!(s: string) -> () [io]

Write s to standard error verbatim; no trailing newline is added. @no-doctest: writes to stderr; side-effecting, with no return value to assert

write!

def write!(s: string) -> Result<(), sys.WriteFailure> [io]

print! with its failures as values and never as program-level outcomes.

print! ends the program without a message when nothing is reading its output, and faults on any other write failure (HANKI.md §6), which is what a command-line program wants. This is for the one that does not: a daemon or a long-lived writer that has to observe its consumer going away and carry on doing something else. @no-doctest: writes to stdout; the failure paths need a closed consumer, which no doctest can arrange

ewrite!

def ewrite!(s: string) -> Result<(), sys.WriteFailure> [io]

The eprint! counterpart of write!. @no-doctest: writes to stderr; the failure paths need a closed consumer, which no doctest can arrange

read_line!

def read_line!() -> Line [io]

Read one line from standard input. Line(s) on a read (trailing \n / \r\n already stripped by the seam), Eof at end of stream. @no-doctest: reads standard input; needs a stream to read, and cannot assert in a doctest

ReadFailure

type ReadFailure
  Failed(string)
  NotUtf8
end

Why a read to end of input stopped early.

readallbytes!

def read_all_bytes!() -> Result<bytes, ReadFailure> [io]

Read standard input to end of input, faithfully: every byte the stream yields, line terminators included, nothing added and nothing removed.

This is the read read_line! cannot express. read_line! strips the terminator, and a caller reassembling its lines cannot tell whether the input ended with a newline, and cannot tell \r\n from \n - which a filter or an editor round-trip has to preserve.

Interleaving: like sys.stdin_read! beneath it, this reads the raw stdin descriptor with no buffering layer, which leaves mixing it with read_line! unable to swallow bytes: it continues from wherever the stream already is. @no-doctest: reads live stdin; the result is the input, which no doctest can supply

read_all!

def read_all!() -> Result<string, ReadFailure> [io]

read_all_bytes! decoded as UTF-8: the form a text filter wants. @no-doctest: reads live stdin; the result is the input, which no doctest can supply