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.
On Linux
Section titled “On Linux”Nothing to start up, the handle is a small int rather than a pointer-sized
one, and the call that closes it is close.
On macOS
Section titled “On macOS”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.
On Windows
Section titled “On Windows”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.
API at a glance
Section titled “API at a glance”Functions: invalid_handle · start · listen_on · accept_on · set_receive_timeout · receive · transmit · connect_to · transmit_bytes · shut · receive_now · nothing_yet
Functions
Section titled “Functions”invalid_handle
Section titled “invalid_handle”pub fn invalid_handle() -> IntA failed call returns -1, and a handle is never negative.
pub fn start() -> BoolNothing 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.
listen_on
Section titled “listen_on”pub fn listen_on(port: Int) -> IntA socket listening on port, or invalid_handle.
accept_on
Section titled “accept_on”pub fn accept_on(server: Int) -> IntWaits for a connection. invalid_handle if the wait failed.
set_receive_timeout
Section titled “set_receive_timeout”pub fn set_receive_timeout(handle: Int, millis: Int) -> BoolHow 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.
receive
Section titled “receive”pub fn receive(connection: Int, buffer: Array<U8>) -> IntReads what has arrived into buffer, and says how many bytes that was.
Zero means the other end closed; negative means the read failed.
transmit
Section titled “transmit”pub fn transmit(connection: Int, text: String) -> IntWrites all of text, waiting for room when the peer is slow, and says how
many bytes went: every one of them, or a negative number if the write
failed. After a failure some of text may have gone, so the connection is
no longer in step with its peer.
A peer that has reset the connection (a killed client, a load balancer)
is a failed write like any other: the write returns a negative number,
and no SIGPIPE stops the program.
connect_to
Section titled “connect_to”pub fn connect_to(host: String, port: Int) -> IntOpens 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.
transmit_bytes
Section titled “transmit_bytes”pub fn transmit_bytes(connection: Int, bytes: Array<U8>) -> IntWrites all of bytes, and says how many went: every one of them, or a
negative number if the write failed, as for transmit.
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.
receive_now
Section titled “receive_now”pub fn receive_now(connection: Int, buffer: Array<U8>) -> IntReads what has already arrived, without waiting for more.
Zero means the other end closed. Negative means nothing can be read now:
-1 because nothing has arrived yet, anything lower because the read
failed (the peer reset the connection, say). A caller that only wants to
stop reading treats every negative alike; one checking whether a
connection is still usable tells them apart with nothing_yet.
nothing_yet
Section titled “nothing_yet”pub fn nothing_yet(read: Int) -> BoolWhether a receive_now result means only that nothing has arrived yet.
What this prevents: a reset connection taken for a quiet one. Both
answer receive_now with a negative; only this one is still usable, and
writing to the other ends the process on the platforms that raise
SIGPIPE.
Khora 0.4.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource