Skip to content

TCP

Connect, listen, and move bytes through TCP sockets.

Updated View as Markdown

Tcp exposes connected sockets and listeners through typed resource handles. It supplies byte transport only: framing and application protocol semantics belong to the caller.

Clients

fn connect(address: string): TcpConnection ! TcpError {
    return Tcp.connect(address)?
}

Addresses use host:port text. Connection establishment may suspend.

Tcp.connect returns a typed TcpError on failure. The installed safe wrapper currently maps negative raw host results to Other { code }; the richer variants document the error vocabulary but may not be produced by every wrapper path yet.

Servers

listener := Tcp.listen("127.0.0.1:8080")?

connection := listener.accept()?

Binding is synchronous in the current wrapper. accept suspends until a client connects.

A sequential loop handles one accepted connection at a time. Spawn a child task when connections should overlap:

listener := Tcp.listen(address)?
for {
    connection := listener.accept()?
    spawn {
        serve(connection)
    }
}

The listener remains owned by the accepting scope. Define cancellation and a maximum number of concurrent children so shutdown and overload behavior are explicit.

Reading

read_into(buffer, max) reserves space and reads directly into a mutable byte list:

mut buffer: []byte = []
count := connection.read_into(buffer, 4096)?
if count == 0 {
    handle_eof()
}

Zero means the peer closed the connection. Positive counts extend the list. The operation may return fewer bytes than requested even when the peer remains open. max <= 0 returns zero without reading.

read_into appends to the buffer’s current tail. Track the newly received region or clear/consume processed bytes according to the framing protocol:

before := buffer.len()
count := connection.read_into(buffer, 4096)?
after := before + count

The read reserves space before suspension, allowing the host to fill the guest allocation directly without a staging copy.

Writing

write_all(slice) sends the full byte view and returns the written count:

written := connection.write_all(response.bytes())?

The host loops until the complete view is sent or an error occurs. An empty slice returns zero without I/O. Use a borrowed slice to avoid copying when possible, and keep its parent allocation alive for the duration of the call.

write_all is not a message boundary. Two writes can be observed as one read, and one write can require multiple reads. Prefix lengths, delimit records, or use a higher-level protocol.

Lifetime

Connections and listeners implement Drop, so normal scope exit, error propagation, and cancellation close their host resources. Explicit close() releases early. Cleanup should remain idempotent.

Do not copy an opaque resource handle into long-lived data and assume each copy owns an independent socket. Treat the listener or connection as a scoped resource and pass it to the task responsible for its lifetime.

Errors

TcpError distinguishes address conflicts, invalid addresses, refused/reset/ aborted connections, missing connection state, timeouts, permission denial, invalid handles, and raw host errors.

TCP is a byte stream. Message framing, text encoding, retries, timeouts, authentication, and application protocols belong above this API.

Connection reset and clean EOF differ: reset is an error; EOF is an Ok(0) read. Retry policy also depends on the operation—reconnecting after a failed connect is different from replaying bytes after a partial application request.

Effects

Connect, accept, read, and write may suspend. Bind and close are synchronous in the current wrapper. Suspending calls retain the guest byte allocation through the host operation; raw pointers remain an implementation detail of the safe API.

For cancellation behavior, see Cancellation. For structured child tasks, see Spawning.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close