Skip to content
You are reading v0.2, which is not the current release. Go to v0.4.

std::net::socket

Sockets: listening, connecting, and moving bytes.

Three files and one module – socket_linux.kh, socket_macos.kh and socket_windows.kh – of which exactly one is ever in a build; khora_db::selected_for_target is the rule and the file’s name is where it is written. The API below is the same on all three, and the sections at the end of this page are what differs underneath it.

No struct crosses the boundary. bind wants a sockaddr_in, which is sixteen bytes; those sixteen bytes are laid out in Khora, in an Array<U8>, and lent as a Ptr for exactly the duration of the call. That is what The ffi design note says a pointer is for, and it is why binding a socket needs nothing added to the runtime.

Nothing to start up, the handle is a small int rather than a pointer-sized one, and the call that closes it is close.

Two differences from Linux, both of them BSD’s rather than Apple’s: a sockaddr_in begins with a length byte where the Linux one puts the low half of the family, and the socket-option numbers are the BSD ones. Everything else – the calls, the sizes, the byte order – is the same, which is why this file is otherwise a copy of socket_linux.kh.

A copy, and that is the mechanism working as designed. A file suffix selects a whole file, so a target that differs in two lines still needs a file of its own. _posix exists for the case where Linux and macOS agree completely, and sockets are not that case. The cost is that a fix to one of these usually belongs in both.

The Berkeley names with Winsock’s differences: WSAStartup before anything else – which is what start is for, and why it is not a no-op here – closesocket rather than close, and a SOCKET that is a pointer-sized handle rather than a small int.

Functions: invalid_handle · start · listen_on · accept_on · set_receive_timeout · receive · transmit · connect_to · transmit_bytes · shut · receive_now

pub fn invalid_handle() -> Int

A failed call returns -1, and a handle is never negative.

pub fn start() -> Bool

Nothing to start on Linux and macOS, where this is true and no more.

It is here because the Windows file’s start is not a no-op: Winsock has to be told which version of itself to be, once per process, and a caller that skipped this would work everywhere except there. Call it anyway.

pub fn listen_on(port: Int) -> Int

A socket listening on port, or invalid_handle.

pub fn accept_on(server: Int) -> Int

Waits for a connection. invalid_handle if the wait failed.

pub fn set_receive_timeout(handle: Int, millis: Int) -> Bool

How long a read will wait before giving up, in milliseconds.

See the note on the Windows one for why a server cannot do without it.

No longer SO_RCVTIMEO, and it could not be. The kernel’s option applies to a call that would have blocked, and a socket the reactor drives never has one — so it goes silently inert the moment the socket is prepared, and the slow client it exists to shed would park a fiber for ever. The runtime keeps the deadline and a scheduler timer enforces it. A read that runs out still comes back negative, which is what every caller here already treats as a connection to give up on.

pub fn receive(connection: Int, buffer: Array<U8>) -> Int

Reads what has arrived into buffer, and says how many bytes that was.

Zero means the other end closed; negative means the read failed.

pub fn transmit(connection: Int, text: String) -> Int

Writes text, and says how many bytes went. Negative if the write failed.

pub fn connect_to(host: String, port: Int) -> Int

Opens a connection to host on port, or invalid_handle.

The first thing here that starts a conversation. Everything else in this module grew from serving: listen_on, accept_on, and nothing that dials. A database driver is the first caller that needs the other direction.

host may be a name or an address; resolution, address family and the platform’s own resolver are the runtime’s, which is why this is one extern rather than getaddrinfo and two sockaddr layouts written three times. The ffi design note is the boundary and khora_net_connect is on the far side of it.

It blocks the fiber’s worker while it connects. A DNS lookup is not something the reactor can wait on. That is once per connection against many queries, which is the right thing to get wrong first — a known cost rather than one left to be found.

pub fn transmit_bytes(connection: Int, bytes: Array<U8>) -> Int

Writes bytes, and says how many went. Negative if the write failed.

transmit takes a String, which is right for a protocol made of text and wrong for one made of bytes: a wire protocol frames its messages with a length nobody wrote as characters. Postgres is the first caller, and a String would have meant deciding what its bytes mean on the way through.

pub fn shut(handle: Int) ->()

Closes a connection, letting the other end finish first.

The same reasoning as the Windows file, and the same shape: closing while the peer is still writing sends an RST, which discards the answer the peer had not read yet. SHUT_WR is 1 here too.

The drain takes what has already arrived and never waits for more. A peer that holds the connection open and says nothing would otherwise hold shut until the kernel abandoned the half-closed connection by itself, which here is tcp_fin_timeout — sixty seconds by default.

pub fn receive_now(connection: Int, buffer: Array<U8>) -> Int

Reads what has already arrived, without waiting for more.

Zero means the other end closed; negative means the read failed or would have blocked, which are one answer here because a caller does the same thing with both.

Khora 0.2.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource