Socket
TCP socket module: a clean interface over the tcp_* builtins.
Wraps the low-level tcp_connect / tcp_send_all / tcp_recv_all / tcp_close builtins with an ergonomic Result-based API. All operations are synchronous. For async use, wrap in task_spawn.
File descriptors are plain Int values. Always call Socket.close when done, ideally via Socket.with_connection which ensures cleanup.
Public API: Socket.connect(host, port) : Result(Int, SocketError) Socket.connect_timeout(host, port, ms) : Result(Int, SocketError) Socket.write(fd, data) : Result((), SocketError) Socket.recv(fd, max_bytes) : Result(String, SocketError) Socket.recv_timeout(fd, max, ms) : Result(String, SocketError) Socket.set_recv_timeout(fd, ms) : Result((), SocketError) Socket.recv_deadline(fd, max, ms) : Result(Option(String), SocketError) Socket.close(fd) : () Socket.with_connection(host, port, f) : Result(a, SocketError) Socket.error_message(e) : String
Types
Functions
Close fd. Silently ignores errors (already-closed fds, etc.).
Open a TCP connection to host:port. Returns Ok(fd) or Err(ConnectionFailed(msg)). The caller must close the fd.
Examples
match Socket.connect("127.0.0.1", 6379) do
Ok(fd) -> use_redis(fd)
Err(e) -> println(Socket.error_message(e))
endOpen a TCP connection to host:port, giving up after timeout_ms if the peer never answers (a SYN dropped by a partition gets no RST, so a plain connect waits out the kernel's SYN retries, a minute or two). timeout_ms <= 0 means no bound. A refused connection still fails at once. Every failure, including the timeout, is Err(ConnectionFailed(msg)).
Examples
Socket.connect_timeout("10.0.0.5", 8080, 2000)Return a human-readable description of a SocketError.
Examples
Socket.error_message(RecvTimeout(500)) -- "receive timed out after 500ms"
Socket.error_message(WriteFailed("EPIPE")) -- "write failed: EPIPE"Receive up to max_bytes from fd, with no per-call deadline.
This still reports Err(RecvTimeout(0)) if the fd carries a deadline from set_recv_timeout and that deadline expires -- the 0 records that the bound came from the descriptor rather than from this call, which had none of its own. Otherwise it blocks until the peer sends or the connection closes. Ok("") means the peer closed the connection (EOF); other failures are Err(RecvFailed(msg)).
Examples
match Socket.recv(fd, 4096) do
Ok("") -> :closed
Ok(data) -> handle(data)
Err(e) -> println(Socket.error_message(e))
endAs recv_timeout, but an expired deadline is Ok(None) rather than an Err.
The same read, reported differently. Err(RecvTimeout(ms)) says a timeout is a kind of failure; Ok(None) says it is the absence of an event, which is what a caller polling a quiet peer usually means. Pick whichever makes the calling code say what it means — a reader that treats silence as normal and one that treats it as an error should not have to share a spelling.
Ok(Some("")) is a clean EOF, as in recv. Other failures are Err(RecvFailed(msg)).
Examples
match Socket.recv_deadline(fd, 4096, 250) do
Ok(None) -> poll_again()
Ok(Some(data)) -> handle(data)
Err(e) -> Socket.close(fd)
endReceive up to max_bytes from fd, waiting at most timeout_ms for the peer to send anything. timeout_ms <= 0 means no timeout.
Returns Err(RecvTimeout(timeout_ms)) when the deadline expires with the peer still silent -- a distinct fact from Err(RecvFailed(...)), because only one of the two is worth retrying. The deadline applies to this call alone and leaves no lasting property on fd; to bound reads made through TLS, use set_recv_timeout instead.
On a peer that accepts the connection and then sends nothing, this returns Err(RecvTimeout(timeout_ms)) rather than blocking.
Examples
match Socket.recv_timeout(fd, 4096, 1000) do
Err(RecvTimeout(_)) -> retry()
other -> other
endBound every subsequent blocking read on fd to timeout_ms (SO_RCVTIMEO). timeout_ms <= 0 clears the deadline.
Unlike recv_timeout this is a persistent property of the descriptor, and that is the point: it is the only mechanism that reaches reads made through OpenSSL, so a TLS client calls it on the fd BEFORE Tls.connect to bound both the handshake and every later Tls.read against a peer that goes silent.
The cost of that reach: a deadline firing part-way through a TLS record leaves the session unusable. On timeout, close the connection -- do not retry the read.
Returns Ok(()) once the option is applied, or Err(SocketOptionFailed(msg)).
Examples
let? _ = Socket.set_recv_timeout(fd, 30000)
Tls.connect(fd, ctx, "example.com")Open a connection, run f(fd), then close the fd and return f's result. f must itself return a Result(a, SocketError); a connection failure is Err(ConnectionFailed(msg)) and f is not called. The fd is closed when f returns normally, but NOT if f panics.
Examples
Socket.with_connection("example.com", 80, fn fd ->
let? _ = Socket.write(fd, "HEAD / HTTP/1.0\r\n\r\n")
Socket.recv(fd, 4096))Write all bytes of data over fd, looping over short writes. Returns Ok(()) or Err(WriteFailed(msg)).
Examples
Socket.write(fd, "PING\r\n")