Recoil logo

Recoil

Ports, files & networking

  • Home
  • Getting Started
  • Guide
  • Reference
  • PARSE
  • State Machines
  • FFI & Systems
  • Ports
  • Packages
  • f00 & Embedded
  • Examples
  • Internals

On this page

Loading…

The port! model

A port! is an open handle — a file, a socket, a TLS session, an HTTP connection. All ports share one runtime shape and one ownership discipline: borrow to read or write, consume to close.

Runtime ports carry a scheme (which outer layer: file, tcp, tls, http, https, dns), a layer-specific handle, an optional inner port for layered schemes, and a close_policy controlling whether closing the outer layer cascades to the inner one.

File ports

Open a file with file/open (wrapped in make port!). Borrow the port with a get-word for reads and writes; close consumes it.

fp: make port! file/open %data.txt
either none? fp [print 0] [print 1]

file/read :fp            ; borrow
file/write :fp "text"     ; borrow
file/close fp            ; consume

Pair the open with a defer so the handle is always released:

fp: make port! file/open %data.txt
defer [file/close fp]

Blocking vs non-blocking reads

Two read styles serve two needs:

  • read/into is async: on an idle socket it returns a would-block result rather than waiting. Use it in a reactor loop.
  • read/blocking waits until bytes arrive or EOF. Use it for a synchronous, line-interactive driver such as a session loop.

Networking layers

The runtime composes networking as layered ports. A TLS, HTTP, or HTTPS port wraps an inner transport and takes ownership of it:

tcp-port: make port! tcp/connect-host host 80
http-port: make port! http/open-wrap tcp-port
; tcp-port is moved into http-port — do not use it again

Construction flows from the bottom up: tcp/connect-host → tls/wrap (or the backend-owned tls/connect-host) → http/open-wrap / https/open-wrap. Each wrapper consumes the port below it.

Cascade close

Close only the topmost layer; the rest cascade when close_policy is PORT_CLOSE_INNER:

  • closing https closes the wrapped tls (and its transport)
  • closing http closes the wrapped tcp
  • closing tls closes the wrapped tcp only for fd-wrap backends

Servers: listen, accept & the reactor

Open a listening TCP port on an empty host, accept connections, and let the runtime reactor block on readiness. A server installs an awake handler and is driven with serve:

; listen on all interfaces, port 8080
server: make port! open tcp://:8080

; accept a ready connection
client: make port! accept :server

The reactor (__recoil_reactor_wait, a select-based wait) wakes ports when they are readable. A transport-agnostic session driver, serve-session :port :vm, runs a line-interactive loop over the blocking read primitive — the same driver works for console://, tcp, and serial:// ports.

The awake handler passed as :fn links only if it is an exported module function — a non-exported function emits an unmangled name and fails to link.

Port error taxonomy

Port failures set a runtime error of type ERR_TYPE_PORT (7) with a numeric code. Layered wrappers preserve one level of cause-chain metadata (runtime/last-error-code and runtime/last-error-cause-code).

CodeConstantMeaning
800ERR_PORT_INVALIDinvalid port
801ERR_PORT_CLOSEDoperation on a closed port
802ERR_PORT_TIMEOUTtimed out
803ERR_PORT_WOULD_BLOCKno data ready (async)
804ERR_PORT_IOI/O failure
805ERR_PORT_PROTOCOLprotocol error
806ERR_PORT_UNSUPPORTEDunknown scheme / unsupported dispatch / TLS fd-wrap unavailable
810–815ERR_PORT_TCP_*resolve, connect, bind, listen, accept, reset
820–824ERR_PORT_TLS_*handshake, verify, cert, protocol, alert
830–833ERR_PORT_HTTP_*status, parse, redirect, protocol
840–841ERR_PORT_DNS_*resolve failure, invalid endpoint host

In the active runtime path, TCP resolve/connect, TLS handshake, and the DNS codes are emitted today; the remaining codes are reserved constants for higher-level diagnostics.

Worked example: an HTTP server

The bundled HTTP example (examples/net/) is a small but complete server built from the language's own building blocks — the request scanner is an FST, the protocol state is an FSM, and parsing is zero-copy.

The parser accepts GET / HTTP/1.0 and HTTP/1.1 requests (with repeated headers and a small Content-Length body), and rejects malformed request lines, missing \r\n\r\n terminators, HTTP/1.1 without Host, and non-integer Content-Length. Routing maps GET / and GET /health to 200, unknown paths/methods to 404, and malformed requests to 400.

parsed: make parse-request-result! parse-request :raw
status: make i32! match parsed [
    ok [request-line headers host] [200]
    error [status-code] [status-code]
]

Request line parts, the header block, and extracted header values are all slice! [char!] views into the original buffer (no copies). See the repository's HTTP server doc and port layering notes for the full surface.

↑

Recoil — an ownership-first, statically-typed language with a static borrow checker.

codeberg.org/rebolek/recoil · boleslav@brezovsky.eu · Apache-2.0