Skip to main content

NET

Constant NET 

Source
pub const NET: &str = "//! TCP connections, and listeners that accept them.\n//!\n//! `connect` opens a `TcpStream` to a `\"host:port\"` address, and `bind` opens\n//! a `TcpListener` whose `accept` waits for connections from other programs.\n//! A `TcpStream` reads and writes through `std/io`\'s `Read` and `Write`, and\n//! failures are `std/io`\'s `IoError`s. Every module sees these names without\n//! an import, and a module\'s own definition of one of them shadows the one\n//! here.\n//!\n//! The operations that wait suspend only the calling task: `connect`,\n//! `accept`, and a stream\'s `read`, `write` and `read_timeout` park the task\n//! until the socket is ready, and other tasks run meanwhile. A host name,\n//! where a numeric address could be given, is looked up first, and the lookup\n//! blocks every task until it finishes.\n//!\n//! A connection over the loopback address: a listener on a port the system\n//! picks, a client that connects to it, and a message read to its end:\n//!\n//! ```\n//! async fn main() {\n//!     let listener = bind(\"127.0.0.1:0\").unwrap() // port 0: the system picks one\n//!     let addr = \"127.0.0.1:${listener.local_port().unwrap()}\"\n//!     let client = connect(addr).await.unwrap()\n//!     let server = listener.accept().await.unwrap()\n//!\n//!     client.write(bytes_from_string(\"ping\")).await.unwrap()\n//!     client.close().await.unwrap() // the server\'s reads now reach the end\n//!     let got = server.read_to_end().await.unwrap()\n//!     assert_eq(got.to_string().unwrap(), \"ping\")\n//!\n//!     server.close().await.unwrap()\n//!     listener.close().await.unwrap()\n//! }\n//! ```\n\n// Nova standard library \u{2014} networking.\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// Error types (`IoError`/`IoErrorKind`) and the `Read`/`Write` traits live in\n// `std/io`; `TcpStream` implements both here, the same split `std/fs`\'s\n// `File` already follows.\n//\n// **This module\'s operations genuinely suspend.** Unlike `std/fs`, whose own\n// module doc comment states plainly that none of its `async fn`s suspend --\n// and not because no poller exists, but because a regular file is not\n// readiness-pollable by `select`/`WSAPoll` on any of this project\'s three CI\n// platforms (docs/adr/0013-io-poller.md\'s IOCP alternative) -- `connect`,\n// `read`, `write`, `read_timeout` and `TcpListener::accept` below run against\n// `crates/nova-runtime/src/net.rs`\'s\n// non-blocking sockets and this crate\'s real poller\n// (`crates/nova-runtime/src/poll.rs`): a call that cannot complete\n// immediately parks the calling task and lets a sibling task run in the\n// meantime, rather than blocking the whole executor\'s thread. See\n// `tests/runtime/net_interleave.nova` for the fixture that actually proves\n// this end to end \u{2014} a merely-correct round trip cannot, because a fully\n// blocking implementation underneath this exact same Nova-level surface\n// would produce the identical bytes.\n//\n// **The `Future<T>` spelling.** `async fn` in a trait *declaration* is\n// `E0900`, exactly as `std/io`\'s own header explains \u{2014} so `Read`/`Write`\n// below are implemented as plain, non-`async` `fn`s returning `Future<T>`,\n// each one calling a private `async fn` and returning its future unawaited.\n//\n// **Every wrapper below awaits its own future-constructor builtin (`net_connect`/\n// `net_read`/`net_write`/`net_read_timeout`/`net_accept`) exactly once, then\n// reads the per-task payload/message slot with no further `.await` in\n// between.** 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 \u{2014} but within one task a second operation still\n// overwrites that same task\'s own slot, so a wrapper that awaited again\n// before reading its result could read back a payload its own later call had\n// already replaced. Keep them straight-line, the same discipline `std/fs`\n// and `std/io` already document for their own wrappers.\n//\n// `net_close`, `net_listen` and `net_local_port` are the exceptions, exactly\n// as `crates/nova-resolver/src/lib.rs`\'s own doc comments for\n// `Builtin::NetClose`, `Builtin::NetListen` and `Builtin::NetLocalPort` state:\n// all three are ordinary status words rather than future constructors, so\n// `TcpStream::close`, `TcpListener::close`, `bind` and\n// `TcpListener::local_port` below call them directly, with no `.await` at all\n// \u{2014} the identical shape `std/fs`\'s `File::close` already uses for\n// `file_close`. The straight-line rule still applies to them: `bind` and\n// `local_port` each read their payload out of the slot with nothing awaited in\n// between.\n\n/// An open TCP connection, from `connect` or `TcpListener::accept`.\n///\n/// It implements `std/io`\'s `Read` and `Write`. A read returns empty bytes\n/// once the peer has closed the connection, a write can write fewer bytes\n/// than it is given, and `flush` does nothing. Copying a `TcpStream` copies\n/// the handle: every copy refers to the same connection. Nothing closes it\n/// for you: call `close` when done, or it stays open until the program exits.\npub record TcpStream { fd: Int }\n\n// Connect to `addr` (\"host:port\"), suspending the calling task until the\n// connection settles (or fails) rather than blocking every other task on\n// this thread for the round trip.\n/// Opens a TCP connection to `addr`, a `\"host:port\"` string such as\n/// `\"127.0.0.1:8080\"`.\n///\n/// An `async fn`: it suspends the calling task until the connection is made\n/// or fails, and other tasks run meanwhile. A host name is looked up first,\n/// blocking every task, and only the first address found is tried. Returns an\n/// `IoError` of kind `ConnectionRefused` if nothing is listening at `addr`,\n/// and `Other` if `addr` is not a valid address. Abandoning a `connect` under\n/// `std/time`\'s `timeout` leaves its socket open until the program exits.\npub async fn connect(addr: String) -> Result<TcpStream, IoError> {\n    let status = net_connect(addr).await\n    if status == 0 { return Ok(TcpStream { fd: decode_count(fs_take_bytes()) }) }\n    Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nimpl TcpStream {\n    // Closes the connection. Idempotent: a second or third call also\n    // returns `Ok(())`, because Nova has no move checking, so `self` by\n    // value cannot stop a second call \u{2014} the identical reasoning `std/fs`\'s\n    // `File::close` documents. Closing a stale or forged `TcpStream` is the\n    // same no-op: the runtime\'s socket table lookup simply removes nothing.\n    /// Closes the connection, so the peer\'s reads reach the end of the stream.\n    ///\n    /// It returns `Ok(())`, also when the stream is already closed: closing\n    /// reports no error. Afterwards, `read`, `write` and `read_timeout` on the\n    /// stream, through any copy, return an `IoError` of kind `Other`.\n    ///\n    /// An `async fn` that never suspends.\n    pub async fn close(self) -> Result<(), IoError> {\n        let status = net_close(self.fd)\n        if status == 0 { return Ok(()) }\n        Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n    }\n\n    // Reads up to `max` bytes, reporting `TimedOut` if `ms` milliseconds\n    // pass with nothing to read first. Otherwise identical to `Read::read`\n    // below, including EOF/short-read semantics: an **empty** result is end\n    // of stream, and a short read is not.\n    //\n    // No `std/fs` analogue exists (no timeout-based read there), so this is\n    // modelled as a second inherent method on `TcpStream`, parallel to\n    // `close` above, rather than folded into the `Read` trait, which has no\n    // room for a third argument.\n    /// Reads up to `max` bytes, as `Read::read` does, but gives up if `ms`\n    /// milliseconds pass with nothing to read.\n    ///\n    /// An `async fn`: it suspends the calling task while it waits, and other\n    /// tasks run meanwhile. Empty bytes mean the peer has closed the\n    /// connection. Returns an `IoError` of kind `TimedOut` if the time runs\n    /// out; with `ms` 0 or negative that happens at once, unless bytes or the\n    /// end of the stream are already there. On a closed stream it returns one\n    /// of kind `Other`. Panics if `max` is negative.\n    pub async fn read_timeout(self, max: Int, ms: Int) -> Result<Bytes, IoError> {\n        let status = net_read_timeout(self.fd, max, ms).await\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}\n\n// Any operation other than `close` against an `fd` the runtime\'s socket\n// table does not recognise \u{2014} already closed, stale, or forged (`TcpStream`\'s\n// field is not privacy-enforced, so `TcpStream { fd: 9999 }` compiles and\n// names no connection this module ever opened) \u{2014} reaches this same\n// `IoError { kind: Other }` by missing the same table lookup, whether the\n// operation is this function, `write_stream` below, or `read_timeout` above.\nasync fn read_stream(s: TcpStream, max: Int) -> Result<Bytes, IoError> {\n    let status = net_read(s.fd, max).await\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 TcpStream {\n    fn read(self, max: Int) -> Future<Result<Bytes, IoError>> { read_stream(self, max) }\n}\n\n// The write\'s byte count crosses the boundary the same way `std/fs`\'s\n// `write_file` decodes its own: 8 little-endian bytes in the payload slot,\n// read back here by that same shared `decode_count`.\n//\n// **May write fewer bytes than given** \u{2014} one non-blocking `Write::write`\n// attempt, not a `write_all` loop, per `net_write`\'s own doc comment\n// (`crates/nova-runtime/src/net.rs`). Mirrors `std/io`\'s `Write::write`\n// contract exactly, deliberately unlike `std/fs`\'s count-less, write-all\n// `write`: a caller here that needs a guaranteed full write must loop on the\n// returned count itself.\nasync fn write_stream(s: TcpStream, buf: Bytes) -> Result<Int, IoError> {\n    let status = net_write(s.fd, buf).await\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// Always `Ok(())`, immediately, with no builtin call at all.\n//\n// **Deliberate, not a stub** \u{2014} the same reasoning `std/io`\'s own\n// `flush_stderr` doc comment gives for a status check it calls \"provably\n// unpinnable\": one `write` here is already one unbuffered syscall (`net_write`\n// wraps a single `std::net::TcpStream::write` attempt), so there is no\n// userspace buffer for a flush to push out, exactly the shape Rust\'s own\n// `impl Write for std::net::TcpStream` uses for the identical reason. There\n// is no `net_flush` builtin and none is added to back a no-op. (This comment\n// used to say \"a sixth one\", counting the five net builtins that existed when\n// it was written; the count was stale within one increment, so it is gone\n// rather than maintained.) **If `std/net` ever grows write buffering, this\n// becomes load-bearing and needs a real intrinsic** \u{2014} a future reader should\n// not mistake this for an oversight.\nasync fn flush_stream(s: TcpStream) -> Result<(), IoError> { Ok(()) }\n\nimpl Write for TcpStream {\n    fn write(self, buf: Bytes) -> Future<Result<Int, IoError>> { write_stream(self, buf) }\n    fn flush(self) -> Future<Result<(), IoError>> { flush_stream(self) }\n}\n\n// A listening socket. Server side of `std/net`, added because Phase 2\n// position 10 (`std/http`) has no transport without it.\n/// A TCP socket that listens for connections, from `bind`.\n///\n/// `accept` waits for the next connection and returns a `TcpStream` for it.\n/// Nothing closes a listener for you: call `close` when done, or it holds its\n/// port until the program exits.\npub record TcpListener { fd: Int }\n\n// Bind and listen on `addr` (\"host:port\"). Pass port 0 to let the kernel\n// choose, then read it back with `local_port`.\n//\n// **A plain `fn`, not an `async fn`: this cannot suspend.** The `bind` and\n// `listen` syscalls do not block, so `net_listen` is an ordinary status word\n// and there is no future to await. Forcing `.await` here would misstate the\n// cost. Note the deliberate exception at `TcpListener::close` below.\n//\n// **One caveat, and it is about `addr` rather than about the syscalls.** A\n// numeric address (\"127.0.0.1:8080\") never blocks. A *hostname* is resolved\n// by the runtime through Rust\'s `ToSocketAddrs`, which is a blocking DNS\n// lookup with no future to park on -- so passing one blocks this whole\n// executor thread, every sibling task included, for as long as resolution\n// takes. `connect` above carries the identical caveat for the identical\n// reason (`resolve_addr` in `crates/nova-runtime/src/net.rs`); neither\n// function makes name resolution asynchronous, and this module does not\n// pretend otherwise. Pass a numeric address on any path where that matters.\n// The two also resolve by *different* policies for a name yielding several\n// addresses -- see `nova_rt_net_listen`\'s own doc comment.\n//\n// An address already in use arrives as `IoError { kind: Other }`, not a\n// distinct kind: `IoErrorKind` has eight variants and `AddrInUse` is not\n// among them, so the most likely failure of this function is discriminable\n// only by `message`, whose text is the operating system\'s own. Adding a\n// variant is a wire-contract change across the runtime\'s `fail` and\n// `std/io`\'s `io_error_kind_of` together, and is deliberately not done here.\n/// Opens a listener on `addr`, a `\"host:port\"` string such as\n/// `\"127.0.0.1:8080\"`.\n///\n/// Port 0 lets the system choose a free port, which `local_port` then\n/// reports. It is not `async`: binding never waits for a peer, though a host\n/// name is looked up first, blocking every task. Returns an `IoError` of kind\n/// `Other` if the address is already in use, which only the error\'s\n/// `message` tells apart, and also if `addr` is not a valid address.\npub fn bind(addr: String) -> Result<TcpListener, IoError> {\n    let status = net_listen(addr)\n    if status == 0 { return Ok(TcpListener { fd: decode_count(fs_take_bytes()) }) }\n    Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n}\n\nimpl TcpListener {\n    // Stops accepting and releases the port. Idempotent, for the same reason\n    // `TcpStream::close` is: Nova has no move checking, so `self` by value\n    // cannot prevent a second call.\n    //\n    // **An `async fn` even though it cannot suspend** -- the one deliberate\n    // exception to the rule stated at `bind` above. It calls the same\n    // `net_close` a stream does, with no `.await`, because the runtime\'s\n    // table removes an entry by key regardless of which kind it holds. It is\n    // declared `async` to match the shipped `TcpStream::close`: a server\n    // closes both handles in sequence, and a reader should not have to\n    // remember which of two identically named socket methods needs `.await`.\n    /// Closes the listener and frees its port.\n    ///\n    /// It returns `Ok(())`, also when the listener is already closed: closing\n    /// reports no error. An `accept` already waiting on the listener goes on\n    /// waiting, and keeps the program from ending, so close a listener only\n    /// when no `accept` is waiting on it.\n    ///\n    /// An `async fn`, like `TcpStream::close`, that never suspends.\n    pub async fn close(self) -> Result<(), IoError> {\n        let status = net_close(self.fd)\n        if status == 0 { return Ok(()) }\n        Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n    }\n\n    // The port this listener is bound to, which is how a caller learns the\n    // kernel\'s choice after binding port 0.\n    //\n    // **A plain `fn`, not an `async fn`: this cannot suspend** -- the same\n    // rule `bind` states, and the same one `close` above deliberately\n    // excepts. Reading a bound port is bookkeeping the kernel already holds,\n    // and unlike `bind` there is no address argument at all, so `bind`\'s\n    // hostname caveat has nothing to apply to here: this one cannot block on\n    // any argument.\n    //\n    // The port arrives in the payload slot rather than as the returned status,\n    // because that status *is* the error kind -- a port carried there would\n    // make `0` ambiguous and every non-zero port indistinguishable from a\n    // failure. So it is read back with the same `decode_count` `bind` uses for\n    // its fd, straight-line with nothing awaited in between.\n    //\n    // Errors as `IoError { kind: Other }` on a closed, stale or forged\n    // listener, by the same missed table lookup every other operation reports\n    // that way.\n    //\n    // **A `TcpListener` whose `fd` actually names a connected stream is that\n    // same case, and is not distinguishable from it.** `fd` is not\n    // privacy-enforced, so `TcpListener { fd: s.fd }` built from a `TcpStream`\n    // compiles; the runtime keeps streams and listeners in one table and its\n    // kind-checked lookup collapses wrong-kind into the identical miss an\n    // absent fd gives. `IoErrorKind` has no wrong-kind variant to report\n    // instead -- the same reason `bind` above gives for `AddrInUse` arriving as\n    // `Other` -- so a stream fd here yields `Other` with the runtime\'s own\n    // \"socket is not open\" message, and no Nova code can tell the two apart.\n    /// Returns the port the listener is bound to: after binding port 0, the\n    /// one the system chose.\n    ///\n    /// It is not `async`, and never waits. Returns an `IoError` of kind\n    /// `Other` if the listener is closed.\n    pub fn local_port(self) -> Result<Int, IoError> {\n        let status = net_local_port(self.fd)\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    // Waits for the next incoming connection, suspending the calling task\n    // rather than blocking the executor.\n    //\n    // **An `async fn` on its merits, not by convention** -- the rule `bind`\n    // states is \"plain `fn` for anything that cannot suspend\", and this is the\n    // one operation on a listener that genuinely can. The `accept` syscall\n    // waits on a peer, so `net_accept` is a future constructor and the\n    // `.await` here is real: while it is parked, sibling tasks on this thread\n    // keep running.\n    //\n    // The returned `TcpStream` is indistinguishable from a connected one, so\n    // `Read`, `Write`, `read_timeout` and `close` above all work on it\n    // unchanged. Its fd arrives in the payload slot rather than as the\n    // returned status, read back with the same `decode_count` `bind` and\n    // `connect` use for their own -- straight-line, with nothing awaited\n    // between the `.await` above and the read, the discipline this module\'s\n    // header states.\n    //\n    // **One socket wait per task, enforced by process abort.** Staging two\n    // I/O waits in a single poll ends the process (`stage_park` in\n    // `crates/nova-runtime/src/task.rs`), so a task parked in `accept` cannot\n    // also be reading or writing a connection: a server needs at least two\n    // tasks -- one accepting, one per connection it serves -- and this\n    // language has no `select` or `race` to fold them into one. `spawn` is the\n    // whole of what makes that possible.\n    //\n    // **No timeout.** There is no `accept_timeout` counterpart to\n    // `TcpStream::read_timeout`, so a task waiting here waits until a\n    // connection arrives or the process ends; an untimed wait is never\n    // reported as a deadlock, and `block_on` cannot return while any task is\n    // parked. A graceful shutdown therefore polls this under `std/time`\'s\n    // `timeout` instead of awaiting it bare, and closes the listener only\n    // once the abandoned accept has returned: closing it under a parked\n    // accept does not wake that accept. `std/http`\'s `Server::listen` is that\n    // shape.\n    //\n    // Errors as `IoError { kind: Other }` on a closed, stale or forged\n    // listener, and on a `TcpListener` whose `fd` actually names a connected\n    // stream -- the same wrong-kind collapse `local_port` above documents at\n    // length, and not distinguishable from the closed case. Both halves of\n    // that promise are measured, in `net.rs`\'s\n    // `accepting_on_a_closed_or_wrong_kind_fd_is_an_error_not_a_park`; until\n    // that test was added this comment asserted a boundary nothing checked.\n    /// Waits for the next incoming connection, and returns a `TcpStream` for\n    /// it.\n    ///\n    /// An `async fn`: it suspends the calling task until a connection\n    /// arrives, and other tasks run meanwhile. It has no time limit, and the\n    /// program does not end while it waits; closing the listener does not end\n    /// the wait either. To stop waiting, run it under `std/time`\'s `timeout`.\n    /// A task waiting here can do nothing else, so a server accepts in one\n    /// task and serves each connection in another, started with `spawn`.\n    /// Returns an `IoError` of kind `Other` if the listener is closed.\n    ///\n    /// Here the server accepts and serves in a task of its own, while this\n    /// one runs the client:\n    ///\n    /// ```\n    /// async fn serve_one(listener: TcpListener) {\n    ///     let conn = listener.accept().await.unwrap()\n    ///     let mut asked = bytes_from_string(\"\")\n    ///     while asked.len() < 4 { // a read may return fewer bytes than were sent\n    ///         asked = asked.concat(conn.read(64).await.unwrap())\n    ///     }\n    ///     assert_eq(asked.to_string().unwrap(), \"ping\")\n    ///     conn.write(bytes_from_string(\"pong\")).await.unwrap()\n    ///     conn.close().await.unwrap()\n    /// }\n    ///\n    /// async fn main() {\n    ///     let listener = bind(\"127.0.0.1:0\").unwrap()\n    ///     let port = listener.local_port().unwrap()\n    ///     let server = spawn(serve_one(listener)) // accept in a task of its own\n    ///     let client = connect(\"127.0.0.1:${port}\").await.unwrap()\n    ///     client.write(bytes_from_string(\"ping\")).await.unwrap()\n    ///     let reply = client.read_to_end().await.unwrap() // until the server closes\n    ///     assert_eq(reply.to_string().unwrap(), \"pong\")\n    ///     server.join().await\n    ///     listener.close().await.unwrap()\n    /// }\n    /// ```\n    pub async fn accept(self) -> Result<TcpStream, IoError> {\n        let status = net_accept(self.fd).await\n        if status == 0 { return Ok(TcpStream { fd: decode_count(fs_take_bytes()) }) }\n        Err(IoError { kind: io_error_kind_of(status), message: fs_last_error_message() })\n    }\n}\n";
Expand description

std/net/lib.nova.