Skip to content

net — stdlib-style ergonomic sockets

The middle layer of the tcp → net → serve stack. net is a thin, blocking, stdlib-shaped wrapper over tcp: it boxes raw handles in a Socket value, adds an Ipv4Address/Ipv6Address/Endpoint addressing model, string-convenience I/O, and a buffered line reader. Depends on tcp. Status-based errors, no exceptions.

Status — Production-ready. Blocking I/O with a full non-blocking escape hatch (poll, per-op timeouts, non-blocking connect), family-agnostic connectHost/dial and dual-stack listen6 all shipped. The IPv6 addressing gap is now closed: an Ipv6Address type ($/parseIpv6, RFC 5952) and a family-carrying Endpoint mean localEndpoint/peerEndpoint of a v6 socket return the real address — $ renders "[::1]:8080" for v6 and "127.0.0.1:8080" for v4. The IPv4 API is unchanged. TLS was extracted into its own tls repo, which simply wraps a net.Socket.

Quickstart

nim
import net

initNet()

let r = dial("example.com", 80)
if r.status == socketConnectConnected:
  var sock = r.socket
  discard sock.sendAll("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")

  var reader = newBufferedSocket(sock)
  let status = reader.recvLine()          # "HTTP/1.0 200 OK"
  while true:
    let line = reader.recvLine()
    if line.len == 0: break                # blank line ends the headers
  let body = reader.readAll()             # drain the rest to EOF
  echo "got ", body.len, " bytes"

  sock.close()

shutdownNet()

API

Addressing types

symbolsignaturewhat it does
Ipv4Addressobject with value*: uint32Host-order IPv4 address.
Ipv6Addressobject with bytes*: array[16, byte]A 128-bit IPv6 address (network-order bytes).
AddressFamilyenumfamilyV4 / familyV6 — which address an Endpoint carries.
Endpointobject with family*: AddressFamily, address*: Ipv4Address, v6*: Ipv6Address, port*: intAn address+port pair; family selects address (v4, default) or v6. The v4 fast path is unchanged.
ipv4proc ipv4(a, b, c, d: int): Ipv4AddressBuild an address from octets; any octet out of 0..255 yields the all-zero address.
anyIpv4proc anyIpv4(): Ipv4AddressThe wildcard 0.0.0.0.
localhostIpv4proc localhostIpv4(): Ipv4Address127.0.0.1.
ipv4Valueproc ipv4Value(ip: Ipv4Address): uint32Extract the raw host-order uint32.
formatIpv4proc formatIpv4(ip: Ipv4Address): stringDotted-decimal text "a.b.c.d". Inverse of parseIpv4.
ipv6FromBytesproc ipv6FromBytes(b: array[16, byte]): Ipv6AddressWrap 16 network-order bytes as an Ipv6Address.
anyIpv6proc anyIpv6(): Ipv6AddressThe unspecified address ::.
localhostIpv6proc localhostIpv6(): Ipv6AddressThe loopback address ::1.
ipv6Bytesproc ipv6Bytes(ip: Ipv6Address): array[16, byte]Extract the raw 16 bytes.
formatIpv6proc formatIpv6(ip: Ipv6Address): stringRFC 5952 canonical text (delegates to tcp).
`$`proc `$`(ip: Ipv4Address): stringDotted-decimal string form.
`$`proc `$`(ip: Ipv6Address): stringRFC 5952 canonical text, e.g. "::1".
`$`proc `$`(endpoint: Endpoint): string"a.b.c.d:port" for v4, bracketed "[::1]:port" for v6.
parseIpv4proc parseIpv4(s: string; dest: var Ipv4Address): boolParse dotted-decimal text; false on malformed input.
parseIpv6proc parseIpv6(s: string; dest: var Ipv6Address): boolParse IPv6 text (full / ::-compressed / v4-mapped tail); false on malformed input.
isIpv6proc isIpv6(endpoint: Endpoint): boolTrue when the endpoint carries an IPv6 address.
invalidEndpointproc invalidEndpoint(): EndpointSentinel endpoint with port == -1.
isValidproc isValid(endpoint: Endpoint): boolTrue when port >= 0.

Socket types

symbolsignaturewhat it does
Socketobject with handle*: TcpHandleA boxed TCP handle — the core value type.
invalidSocketproc invalidSocket(): SocketSentinel invalid socket.
isValidproc isValid(s: Socket): boolWhether the socket wraps a live handle.
SocketConnectStatusenumsocketConnectFailed / socketConnectInProgress / socketConnectConnected.
SocketConnectResultobject with socket*: Socket, status*: SocketConnectStatus, errorCode*: intReturned by the non-blocking / timeout / dial connect paths.
SocketPollRequestobject with read*: bool, write*: boolWhich readiness events to wait for.
SocketPollResultobject with read*, write*, error*, hangup*, invalid*: boolReadiness flags returned by poll.
BufferedSocketobject with socket*: Socket (+ private buffer/pos)Buffered line-oriented reader over a Socket.

Lifecycle & errors

symbolsignaturewhat it does
initNetproc initNet()Initialize the network subsystem (delegates to initTcp; needed on Windows).
shutdownNetproc shutdownNet()Tear down the subsystem.
lastNetErrorCodeproc lastNetErrorCode(): intLast platform socket error code for this thread.
lastNetErrorKindproc lastNetErrorKind(): TcpErrorKindClassified last error.
classifyNetErrorCodeproc classifyNetErrorCode(code: int): TcpErrorKindClassify an arbitrary error code.
netErrorWouldRetryproc netErrorWouldRetry(code: int): boolWould-block / retryable (EAGAIN/EWOULDBLOCK).
netErrorTimedOutproc netErrorTimedOut(code: int): boolTimeout error.
netErrorInterruptedproc netErrorInterrupted(code: int): boolInterrupted (EINTR).
netErrorDisconnectedproc netErrorDisconnected(code: int): boolPeer-disconnect error.

Listening & accepting

symbolsignaturewhat it does
listenproc listen(port: int; backlog = 128): SocketListen on 0.0.0.0:port.
listenproc listen(ip: Ipv4Address; port: int; backlog = 128): SocketListen bound to a specific IPv4 address.
listen6proc listen6(port: int; backlog = 128; dualStack = true): SocketIPv6 listener; with dualStack one socket also serves IPv4-mapped clients.
acceptproc accept(server: Socket): SocketAccept the next connection (invalidSocket() if server is invalid).
acceptWithPeerproc acceptWithPeer(server: Socket; peer: var Endpoint): SocketAccept and report the peer endpoint.

Connecting

symbolsignaturewhat it does
connectproc connect(hostOrderAddr: uint32; port: int): SocketBlocking connect to a raw host-order address.
connectproc connect(ip: Ipv4Address; port: int): SocketBlocking connect to an Ipv4Address.
connectLocalhostproc connectLocalhost(port: int): SocketBlocking connect to 127.0.0.1:port.
connectHostproc connectHost(host: string; port: int): SocketResolve host (A and AAAA) and connect to the first address that accepts.
dialproc dial(host: string; port: int): SocketConnectResultFamily-agnostic connect that sweeps the full getaddrinfo set (happy-eyeballs-lite); on failure errorCode is the last connect error.
resolveIpv4proc resolveIpv4(host: string; dest: var Ipv4Address): boolResolve a hostname to a single IPv4 address.
connectNonBlockingproc connectNonBlocking(hostOrderAddr: uint32; port: int): SocketConnectResultNon-blocking connect; complete it with finishConnect.
connectNonBlockingproc connectNonBlocking(ip: Ipv4Address; port: int): SocketConnectResultAs above, from an Ipv4Address.
connectLocalhostNonBlockingproc connectLocalhostNonBlocking(port: int): SocketConnectResultNon-blocking connect to localhost.
connectHostNonBlockingproc connectHostNonBlocking(host: string; port: int): SocketConnectResultResolve then non-blocking-connect (IPv4 path).
connectTimeoutproc connectTimeout(hostOrderAddr: uint32; port: int; millis: int): SocketConnectResultBlocking connect bounded by millis.
connectTimeoutproc connectTimeout(ip: Ipv4Address; port: int; millis: int): SocketConnectResultAs above, from an Ipv4Address.
finishConnectproc finishConnect(socket: Socket; errorCode: var int): boolCheck whether a non-blocking connect completed; reports the error code.
finishConnectproc finishConnect(socket: Socket): boolValue form of the above.

Reading & writing

symbolsignaturewhat it does
recvIntoproc recvInto(socket: Socket; buf: pointer; len: int): intSingle read into a caller-owned buffer; bytes read, 0 at EOF, -1 on error.
sendFromproc sendFrom(socket: Socket; buf: pointer; len: int): intSingle write from a caller-owned buffer.
sendAllFromproc sendAllFrom(socket: Socket; buf: pointer; len: int): intWrite the whole buffer, looping over partial writes.
recvproc recv(socket: Socket; maxBytes: int): stringRead up to maxBytes into a string, looping until maxBytes / EOF / would-block — no hidden 8192 cap.
readAllproc readAll(socket: Socket): stringDrain the whole stream to EOF into a string.
sendproc send(socket: Socket; data: string): intSend the whole string unless the socket errors; returns bytes sent.
sendAllproc sendAll(socket: Socket; data: string): boolsend == data.len; true iff the whole string went out.

Buffered reader

symbolsignaturewhat it does
newBufferedSocketproc newBufferedSocket(socket: Socket): BufferedSocketWrap a socket in a buffered line reader.
bufferedSocketproc bufferedSocket(socket: Socket): BufferedSocketAlias for newBufferedSocket.
recvLineproc recvLine(reader: var BufferedSocket): stringRead one CRLF/LF-terminated line (terminator stripped); over-read bytes stay buffered; "" at EOF.
recvproc recv(reader: var BufferedSocket; maxBytes: int): stringRead up to maxBytes, draining the buffer first so it composes with recvLine.
readAllproc readAll(reader: var BufferedSocket): stringRead everything left: buffer first, then the socket to EOF.

Non-blocking, polling & timeouts

symbolsignaturewhat it does
setBlockingproc setBlocking(socket: Socket; blocking: bool): boolSet blocking mode.
setNonBlockingproc setNonBlocking(socket: Socket): boolSwitch the socket to non-blocking.
pollproc poll(socket: Socket; request: SocketPollRequest; timeoutMillis: int; ready: var SocketPollResult): intWait for read/write readiness; fills ready, returns the poll count (-1 if invalid).
waitReadableproc waitReadable(socket: Socket; timeoutMillis: int): boolBlock until readable or timeout.
waitWritableproc waitWritable(socket: Socket; timeoutMillis: int): boolBlock until writable or timeout.
setReadTimeoutMillisproc setReadTimeoutMillis(socket: Socket; millis: int): boolPer-read timeout.
setWriteTimeoutMillisproc setWriteTimeoutMillis(socket: Socket; millis: int): boolPer-write timeout.
setTimeoutMillisproc setTimeoutMillis(socket: Socket; millis: int): boolSet both read and write timeouts.

Socket options & introspection

symbolsignaturewhat it does
localEndpointproc localEndpoint(socket: Socket): EndpointThe socket's local address+port (family-aware — a v6 socket yields a v6 endpoint).
peerEndpointproc peerEndpoint(socket: Socket): EndpointThe connected peer's address+port (family-aware — v4 or v6).
setNoDelayproc setNoDelay(socket: Socket; enabled = true): boolToggle TCP_NODELAY (Nagle off).
setKeepAliveproc setKeepAlive(socket: Socket; enabled = true): boolToggle SO_KEEPALIVE.
socketErrorCodeproc socketErrorCode(socket: Socket; errorCode: var int): boolRead SO_ERROR into errorCode.
socketErrorCodeproc socketErrorCode(socket: Socket): intValue form of the above (-1 if invalid).

Shutdown & close

symbolsignaturewhat it does
shutdownReadproc shutdownRead(socket: Socket): boolHalf-close the read side.
shutdownWriteproc shutdownWrite(socket: Socket): boolHalf-close the write side.
shutdownBothproc shutdownBoth(socket: Socket): boolHalf-close both directions.
closeproc close(socket: Socket)Close the socket (no-op if already invalid).
closeAndInvalidateproc closeAndInvalidate(socket: var Socket)Close and reset the handle to InvalidTcpHandle.

Design notes

  • Value-typed sockets. A Socket is just a boxed TcpHandle; there is no hidden allocation or finalizer. You close explicitly, and every op on an invalid socket returns a sentinel (-1, false, invalidSocket()) rather than raising.
  • Status-based errors, no exceptions. Failures surface as return codes plus the thread-local lastNetError* family, mirroring tcp's TcpErrorKind model.
  • Blocking by default, non-blocking on demand. The plain recv/send/connect paths block; setNonBlocking + poll/waitReadable + connectNonBlocking/ finishConnect give a complete non-blocking escape hatch over the same Socket.
  • Family-agnostic reach. connectHost/dial follow both A and AAAA records via tcp's connectHostTcp, and listen6 gives one dual-stack listener for both families — so callers rarely branch on address family.
  • Family-carrying endpoints. Endpoint tags itself familyV4/familyV6, so localEndpoint/peerEndpoint of a v6 socket return the real IPv6 address and $ renders it bracketed ("[::1]:8080"). The addition is backward-compatible: Endpoint(address: someIpv4, port: p) still builds a familyV4 endpoint and .address keeps working, so the IPv4 fast path is untouched.
  • Caller-owned buffers underneath, strings on top. The raw recvInto/sendFrom pair takes your buffer; recv/readAll/send and BufferedSocket add string-convenience and line framing on top without a per-call cap surprise.
  • TLS lives elsewhere. TLS was extracted into its own tls repo; a TLS session simply wraps a net.Socket, keeping this layer plaintext-only.

Requirements

  • Nimony toolchain (aowl / nimony compiler).
  • Dependency: aoughwl/tcp (the underlying blocking socket layer). No C libraries beyond the platform socket API that tcp binds.

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.