pub const IO: &str = "//! I/O errors, the `Read` and `Write` traits, and the standard streams.\n//!\n//! The operations here and in `std/fs` and `std/net` that can fail report a\n//! failure as an `IoError`: an `IoErrorKind` saying what went wrong, and a\n//! message for a person to read. `Read` and `Write` move bytes a piece at a\n//! time. `stdin()`, `stdout()` and `stderr()` return the process\'s standard\n//! streams, which implement them, as `std/fs`\'s `File` and `std/net`\'s\n//! `TcpStream` do. Every module sees these names without an import, and a\n//! module\'s own definition of one of them shadows the one here.\n//!\n//! The methods of `Read` and `Write` return a `Future` for the caller to\n//! await. On the standard streams they never suspend: each call blocks every\n//! task until the operating system has answered.\n//!\n//! Writing to standard output through `Write`, and telling an `IoError` by\n//! its kind:\n//!\n//! ```\n//! async fn main() {\n//! // `write` returns how many bytes it took, which can be fewer than given.\n//! let out = stdout()\n//! let n = out.write(bytes_from_string(\"hello\\n\")).await.unwrap()\n//! assert(n >= 1 && n <= 6, \"some of the six bytes were written\")\n//! out.flush().await.unwrap()\n//!\n//! // A failure is an `IoError`: match on its `kind`, not its `message`.\n//! let err = match read_to_string(\"${temp_dir()}/nova-std-io/absent.txt\").await {\n//! Ok(_) => panic(\"nothing should be there\")\n//! Err(e) => e\n//! }\n//! let not_found = match err.kind {\n//! NotFound => true\n//! _ => false\n//! }\n//! assert(not_found, \"expected NotFound, got: ${err.message}\")\n//! }\n//! ```\n\n// Nova standard library \u{2014} I/O error types, and the `Read`/`Write` traits over\n// the three standard streams.\n//\n// Compiled as an implicit module and glob-imported into every user module, so\n// these names need no `import` (see docs/adr/0004-stdlib-compile-model.md).\n// A user definition of the same name shadows the one here.\n//\n// `File`, `open` and `OpenOptions` live in `std/fs`, not here: they need the\n// handle-lifetime design that module\'s own doc comment describes. `Stdin`,\n// `Stdout` and `Stderr` below are process-global, always open and never\n// closed, so they exercise both traits below with none of it.\n//\n// **The `Future<T>` spelling.** `async fn` in a trait *declaration* is\n// `E0900`, so `Read`/`Write` below are declared `fn ... -> Future<T>`\n// instead of `async fn ... -> T`. Calling an `async fn` without `.await`\n// produces its `Future` without running it, so a plain, non-`async` `fn` can\n// still return one, unawaited -- every method on both traits follows this\n// same shape, including `read_to_end`\'s default body, which returns the\n// `Future` of the private, generic `read_all` rather than `await`ing it\n// itself (an `async fn` default body could not: it would need to `await` to\n// produce a plain `T`, and a non-`async` `fn` has no `await` available to it).\n//\n// **Every wrapper below is straight-line**: it calls its builtin and reads\n// the per-task payload/message slot with no `.await` between the two. Those\n// slots are per-task, keyed on the polling task\n// (`crates/nova-runtime/src/fs.rs`\'s `SLOTS`/`slot_index`), so a sibling task\n// cannot clobber them -- but *within* one task, a second operation still\n// overwrites that same task\'s own slot, so a wrapper that awaited\n// mid-sequence could read back a payload its own later call had already\n// replaced. `fs_take_bytes`/`fs_last_error_message` keep their `fs_` prefix\n// for history, not scope: the slot table has been a general per-task\n// boundary facility since increment 3a, not a filesystem-only one, and\n// `std/io` reaches both directly because `Builtin::STD_ONLY` is seeded into\n// every std module\'s scope, not only `std/fs`\'s.\n\n// Why an operation failed. `ConnectionRefused` is unused by `std/fs` and is\n// carried so \u{a7}4\'s network I/O needs no further addition later.\n//\n// `AlreadyExists` and `InvalidData` are additions to the list in\n// `nova-spec/20-STDLIB.md` \u{a7}4; see docs/adr/0011-io-error-kinds.md.\n/// What went wrong in a failed I/O operation, as an `IoError` reports it.\n///\n/// Match on it to handle a failure by its cause. Failures with no variant of\n/// their own are `Other`, and only the error\'s `message` tells them apart.\npub type IoErrorKind =\n /// A file or directory the operation needs does not exist.\n | NotFound\n /// The operating system did not permit the operation.\n | PermissionDenied\n /// Something the operation was to create already exists.\n | AlreadyExists\n /// The data is not valid for the operation, as when `read_to_string`\n /// reads a file that is not UTF-8.\n | InvalidData\n /// The operation was interrupted before it finished.\n | Interrupted\n /// A time limit passed before the operation finished, such as\n /// `TcpStream::read_timeout`\'s.\n | TimedOut\n /// The other end refused the connection: usually nothing is listening at\n /// the address `connect` was given.\n | ConnectionRefused\n /// Any other failure, among them an address already in use, an address\n /// that is not valid, a broken connection, and a `File` or `TcpStream`\n /// that is closed.\n | Other\n\n// A failed I/O operation: a kind to branch on, and the operating system\'s own\n// message for diagnosis.\n//\n// `message` is platform-specific text, so a test asserting output should match\n// on `kind` and normalise `message` -- the same treatment\n// `tests/runtime/nova_test.stdout` already applies to a Windows NTSTATUS.\n/// A failed I/O operation: what kind of failure it was, and a description of\n/// it.\n///\n/// The functions of `std/io`, `std/fs` and `std/net` that can fail return\n/// one in their `Err`.\npub record IoError {\n /// What kind of failure this is.\n pub kind: IoErrorKind,\n /// A description of the failure for a person to read, usually the\n /// operating system\'s own, so its wording differs from one system to\n /// another. Match on `kind` rather than on `message`.\n pub message: String,\n}\n\n// Map a runtime status code to its kind.\n//\n// **This function is one half of a wire contract.** The other half is\n// `fail` in `crates/nova-runtime/src/fs.rs`, which produces these codes\n// from `std::io::ErrorKind`. The two are independent copies of one numbering,\n// which is the shape that has already produced a miscompile in this project.\n//\n// **This function\'s own mapping (every arm below, including the unmapped-code\n// fallback to `Other`) is pinned per-code by `fs_io_types.nova`.** The Rust\n// side that produces these codes from a real OS condition is pinned for only\n// four of the eight kinds -- `NotFound`, `AlreadyExists`, `InvalidData`, and\n// (Windows only) `PermissionDenied`; the rest are either unreachable from\n// `std/fs` or reachable but not yet exercised by a fixture. See `fs.rs`\'s\n// status-constant doc comment and `docs/adr/0011-io-error-kinds.md` for the\n// full split -- this comment previously claimed all eight were \"pinned\n// together by a fixture per kind,\" which was false for five of them\n// (final review, I1).\n//\n// An unrecognised code is `Other` rather than a panic: a status this function\n// does not know is a runtime bug, and mapping it to `Other` keeps a diagnosable\n// error flowing instead of ending the process.\n/// Returns the `IoErrorKind` that the status code `code` stands for: 1\n/// `NotFound`, 2 `PermissionDenied`, 3 `AlreadyExists`, 4 `InvalidData`, 5\n/// `Interrupted`, 6 `TimedOut`, 7 `ConnectionRefused`, and `Other` for any\n/// other code.\n///\n/// std\'s I/O functions use it to build their `IoError`s.\npub fn io_error_kind_of(code: Int) -> IoErrorKind {\n if code == 1 { return NotFound }\n if code == 2 { return PermissionDenied }\n if code == 3 { return AlreadyExists }\n if code == 4 { return InvalidData }\n if code == 5 { return Interrupted }\n if code == 6 { return TimedOut }\n if code == 7 { return ConnectionRefused }\n Other\n}\n\n/// A source of bytes, read a piece at a time.\n///\n/// `Stdin`, `std/fs`\'s `File` and `std/net`\'s `TcpStream` implement it. Its\n/// methods return a `Future`: await it for the result.\npub trait Read {\n // Up to `max` bytes. An **empty** result means end of stream. **A short\n // read is not EOF** -- a pipe or a terminal may hand back far fewer bytes\n // than `max` and still have more to give later, so a caller (including\n // `read_to_end`\'s default below) must keep reading on anything\n // non-empty and stop only on a genuinely empty result.\n /// Reads up to `max` bytes and returns them; empty bytes mean the end of\n /// the stream.\n ///\n /// A read can return fewer than `max` bytes even when more are coming, so\n /// only an empty result marks the end. A `max` of 0 returns empty bytes\n /// too. Room for `max` bytes is allocated before the read, however few\n /// arrive.\n ///\n /// Returns an `IoError` if the read fails: of kind `Other` for a `File` or\n /// `TcpStream` that is closed. A `TcpStream` read suspends the calling\n /// task until there is something to read; a `Stdin` or `File` read blocks\n /// every task until it returns. std\'s implementations panic if `max` is\n /// negative.\n fn read(self, max: Int) -> Future<Result<Bytes, IoError>>\n\n // Reads until end of stream and returns everything concatenated in\n // order. Defaults to the private `read_all`, over this trait\'s own\n // `read` -- see that function for the loop, and `CHUNK` for the size it\n // reads at a time.\n /// Reads to the end of the stream and returns every byte read, in order.\n ///\n /// It reads until a read returns empty bytes, so on `Stdin` or a\n /// `TcpStream` it waits for the other end to close. At the end already, it\n /// returns empty bytes. If a read fails, it returns that read\'s `IoError`,\n /// and the bytes read before it are lost.\n fn read_to_end(self) -> Future<Result<Bytes, IoError>> { read_all(self) }\n}\n\n/// A destination for bytes, written a piece at a time.\n///\n/// `Stdout`, `Stderr`, `std/fs`\'s `File` and `std/net`\'s `TcpStream`\n/// implement it. Its methods return a `Future`: await it for the result.\npub trait Write {\n // Writes as much of `buf` as one underlying write accepts, and reports\n // exactly how many bytes that was -- **not necessarily all of `buf`**.\n // Mirrors Rust\'s own possibly-partial `Write::write`, deliberately\n // unlike `std/fs`\'s count-less, write-all `write`. A caller that needs a\n // guaranteed full write must loop on the returned count itself; this\n // method does not do it for you.\n /// Writes as much of `buf` as one write accepts, and returns how many\n /// bytes that was.\n ///\n /// The count can be less than `buf.len()`: to write all of `buf`, call\n /// `write` again with the bytes not yet written. An empty `buf` writes\n /// nothing and returns 0. Returns an `IoError` if the write fails: of kind\n /// `Other` for a `File` or `TcpStream` that is closed, and for a broken\n /// connection. A `TcpStream` write suspends the calling task until there\n /// is room to write; the others block every task until they return.\n ///\n /// Writing all of some bytes, a partial write at a time:\n ///\n /// ```\n /// // `write` can take fewer bytes than it is given, so writing all of them\n /// // means calling it again with the rest.\n /// async fn write_all(f: File, data: Bytes) {\n /// let mut rest = data\n /// while rest.len() > 0 {\n /// let n = f.write(rest).await.unwrap()\n /// rest = rest.slice(n, rest.len())\n /// }\n /// }\n ///\n /// async fn main() {\n /// let dir = \"${temp_dir()}/nova-std-io\"\n /// create_dir_all(dir).await.unwrap()\n /// let f = open(\"${dir}/out.txt\", OpenOptions::writing()).await.unwrap()\n /// write_all(f, bytes_from_string(\"line\\n\".repeat(1000))).await\n /// f.close().await.unwrap()\n /// assert_eq(read(\"${dir}/out.txt\").await.unwrap().len(), 5000)\n /// # remove_dir_all(dir).await.unwrap()\n /// }\n /// ```\n fn write(self, buf: Bytes) -> Future<Result<Int, IoError>>\n\n /// Writes out any bytes the writer is holding in a buffer.\n ///\n /// Of std\'s writers only `Stdout` holds any: on the others it does\n /// nothing, and on a `File` it does not wait for the data to reach the\n /// disk. Returns an `IoError` if the held bytes cannot be written, or for\n /// a `File` that is closed.\n fn flush(self) -> Future<Result<(), IoError>>\n}\n\n// The process\'s standard input. Process-global, always open, never closed --\n// reading it more than once keeps reading from wherever the last read left\n// off, the same as calling `std::io::stdin()` more than once in Rust.\n//\n// A `read` here blocks the whole executor for as long as the OS read takes,\n// so a terminal with nothing typed yet stalls every other task on the thread,\n// not only this one. This used to say \"there is no I/O poller yet\"; amended\n// 2026-08-16 (branch `io-poller-std-net`), because one now exists\n// (`crates/nova-runtime/src/poll.rs`, docs/adr/0013-io-poller.md). The\n// consequence above is unchanged and this one really is still just unbuilt --\n// unlike `std/fs`, whose reason is structural, stdin is in principle a handle\n// this poller\'s mechanism could watch, but nothing wires `std/io` to it:\n// `std/net` is the poller\'s only caller. See\n// `docs/adr/0009-async-execution-model.md` \u{a7}1\'s 2026-08-16 amendment, which\n// records both halves.\n/// The process\'s standard input, which `stdin()` returns.\n///\n/// It is always open, and every `Stdin` reads the same stream: a read\n/// continues where the last one stopped. A read blocks every task until\n/// input arrives or the stream ends.\npub record Stdin {}\n\n/// Returns the process\'s standard input, to read with `Read`.\npub fn stdin() -> Stdin { Stdin {} }\n\nasync fn read_stdin(s: Stdin, max: Int) -> Result<Bytes, IoError> {\n let status = io_stdin_read(max)\n if status == 0 { return Ok(fs_take_bytes()) }\n Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nimpl Read for Stdin {\n fn read(self, max: Int) -> Future<Result<Bytes, IoError>> { read_stdin(self, max) }\n}\n\n// The process\'s standard output. Process-global, always open, never closed.\n/// The process\'s standard output, which `stdout()` returns.\n///\n/// It is always open, and its writes share one buffer with `print` and\n/// `println`, so their output stays in order. A newline sends out everything\n/// up to it; bytes after the last newline can wait in the buffer until a\n/// later newline or `flush`. They can be lost if the program panics or\n/// returns from `main` before then, so flush before it ends; `exit` flushes\n/// them.\npub record Stdout {}\n\n/// Returns the process\'s standard output, to write with `Write`.\npub fn stdout() -> Stdout { Stdout {} }\n\n// The write\'s byte count crosses the boundary as 8 little-endian bytes in the\n// payload slot (`crates/nova-runtime/src/io.rs`\'s `stash_count`), not in the\n// status word -- decoded here by `decode_count`, which reads all eight,\n// rather than by reading a single byte, which would silently truncate any\n// count at or above 256.\nasync fn write_stdout(s: Stdout, buf: Bytes) -> Result<Int, IoError> {\n let status = io_stdout_write(buf)\n if status == 0 { return Ok(decode_count(fs_take_bytes())) }\n Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nasync fn flush_stdout(s: Stdout) -> Result<(), IoError> {\n let status = io_stdout_flush()\n if status == 0 { return Ok(()) }\n Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nimpl Write for Stdout {\n fn write(self, buf: Bytes) -> Future<Result<Int, IoError>> { write_stdout(self, buf) }\n fn flush(self) -> Future<Result<(), IoError>> { flush_stdout(self) }\n}\n\n// The process\'s standard error. Process-global, always open, never closed.\n/// The process\'s standard error, which `stderr()` returns.\n///\n/// It is always open, and not buffered: each write goes out at once.\npub record Stderr {}\n\n/// Returns the process\'s standard error, to write with `Write`.\npub fn stderr() -> Stderr { Stderr {} }\n\n// Mirrors `write_stdout` exactly, against the other stream.\nasync fn write_stderr(s: Stderr, buf: Bytes) -> Result<Int, IoError> {\n let status = io_stderr_write(buf)\n if status == 0 { return Ok(decode_count(fs_take_bytes())) }\n Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\n// Mirrors `flush_stdout` exactly, against the other stream.\nasync fn flush_stderr(s: Stderr) -> Result<(), IoError> {\n let status = io_stderr_flush()\n if status == 0 { return Ok(()) }\n Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nimpl Write for Stderr {\n fn write(self, buf: Bytes) -> Future<Result<Int, IoError>> { write_stderr(self, buf) }\n fn flush(self) -> Future<Result<(), IoError>> { flush_stderr(self) }\n}\n\n// How many bytes `read_all` asks for per `read` call while draining a `Read`\n// to end of stream. Stated once, here, and nowhere else -- a size duplicated\n// between a doc comment and the code is a drift this project has repeatedly\n// had to correct.\nconst CHUNK: Int = 4096\n\n// `read_to_end`\'s default body, over any `Read`. Loops `read(CHUNK)` and\n// concatenates every non-empty result in order, stopping on the first\n// genuinely empty one (`read`\'s own EOF contract) -- never on a short one,\n// which is not EOF (see `Read::read`\'s own doc comment). Getting that\n// backwards -- stopping as soon as a result is shorter than `CHUNK` rather\n// than only when it is empty -- silently truncates the result, and does so\n// against the *common* case, not an edge case: a real pipe or terminal\n// rarely hands back a chunk exactly `CHUNK` bytes long, so nearly every read\n// from one looks \"short\" by that broken test.\nasync fn read_all<T: Read>(reader: T) -> Result<Bytes, IoError> {\n let mut acc = bytes_from_ints([])\n while true {\n let chunk = reader.read(CHUNK).await\n match chunk {\n Ok(bytes) => {\n if bytes.len() == 0 { return Ok(acc) }\n acc = acc.concat(bytes)\n }\n Err(e) => return Err(e)\n }\n }\n Ok(acc)\n}\n\n// Decodes a payload integer -- 8 little-endian bytes in the payload slot --\n// back into the `Int` it encodes, folding from the most significant byte\n// down. Four separate places in the runtime write that encoding, and they are\n// named at the end of this comment rather than cited here, because each\n// produces the payloads of exactly one module. Reading only the first byte --\n// `fs_take_bytes().byte_at(0)` -- would silently truncate any count at or\n// above 256; a single write can exceed that.\n//\n// **This fold RELIES ON `Int` ARITHMETIC WRAPPING, and the reliance is not\n// optional -- do not add an overflow check to this loop.** For a non-negative\n// payload no intermediate exceeds the final value, which is why this comment\n// used to claim the fold needs no more range than its result: true of every\n// payload `std/io`, `std/fs` and `std/net` produce, and false as a general\n// statement. `std/crypto`\'s `random_int` is the first caller whose payload\n// can be negative, and for a drawn `0 - 1` -- eight `0xFF` bytes -- the fold\n// leaves `Int` range one step before it finishes: `n` is `2^56 - 1` after\n// seven iterations, so the eighth multiply reaches `2^64 - 256`, the largest\n// value the fold forms short of its own result, and the closing add reaches\n// `2^64 - 1`, which is that result and not an intermediate at all. Both of\n// those steps are out of range, and it is wrapping at both that carries the\n// fold to `-1`. An overflow check here would therefore corrupt every negative\n// draw while this module\'s own tests stayed green, because none of their\n// payloads is ever negative. `tests/runtime/crypto_random.nova` is what does\n// exercise it: it draws `random_int(0 - 3, 3)` two hundred times, roughly\n// three in seven of which are negative, checks each against both ends, and\n// its golden asserts `bounded true`.\n//\n// `pub` because this is the whole tree\'s payload-integer decoder rather than\n// this module\'s own. Every std module that reads an `Int` back out of the\n// payload slot calls this same function instead of growing a second\n// Nova-level decoder: this module for the two stream writes above, `std/fs`\n// for `File::open`\'s new fd and its `Write::write` byte count, `std/net` for\n// `connect`\'s and `accept`\'s stream fds, `bind`\'s listener fd, `local_port`\n// and its own write count, and `std/crypto` for `random_int`\'s drawn value.\n// All of them cross the boundary the identical way, 8 little-endian bytes in\n// the payload slot, because the status word carries the error kind instead --\n// `IoErrorKind` for this module, `std/fs` and `std/net`, and\n// `CryptoErrorKind` for `std/crypto`, which maps the same status word through\n// its own `crypto_error_kind_of`.\n//\n// **The sharing stops at this function.** One level down, on the runtime\n// side, four separate producers write that same encoding, one per module, and\n// only three of them are even a named helper:\n// `crates/nova-runtime/src/io.rs`\'s `stash_count` for this module\'s two\n// stream writes, `crates/nova-runtime/src/file.rs`\'s `stash_i64` for\n// `std/fs`, `crates/nova-runtime/src/net.rs`\'s `stash_i64` for `std/net` -- a\n// second and separate definition of the same one-line body, not a call into\n// `file.rs`\'s -- and `crates/nova-runtime/src/crypto.rs` for `random_int`,\n// which stashes `value.to_le_bytes()` inline with no helper at all. So what\n// the call sites listed above actually share is the *encoding* -- 8\n// little-endian bytes in the payload slot -- plus this one decoder on the\n// Nova side; there is no shared encoder to point at, and citing any single\n// producer here would be wrong for every module but one. `net.rs`\'s and\n// `file.rs`\'s doc comments each record that their copy is a copy.\n/// Returns the integer that `b` holds in little-endian order: its first byte\n/// is the least significant.\n///\n/// Eight bytes can hold any `Int`, a negative one in two\'s complement. Fewer\n/// give a number that is not negative, empty bytes give 0, and bytes after\n/// the eighth do not change the result. std uses it to decode the integers,\n/// such as byte counts, that its I/O operations report.\npub fn decode_count(b: Bytes) -> Int {\n let ints = b.to_ints()\n let mut n = 0\n let mut i = ints.len() - 1\n while i >= 0 {\n n = n * 256 + ints[i]\n i = i - 1\n }\n n\n}\n";Expand description
std/io/lib.nova.