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