TCP
Luma provides TCP networking through the tcp module.
A TCP connection is an active value — tcp.connect() establishes a real network connection immediately.
Reading and writing adapt automatically based on your types.
Connecting
Basic connection
sock: tcp = tcp.connect("localhost", 3306)Connects to the given host and port over TCP.
Panics if the connection fails (host unreachable, port closed, DNS failure).
Safe connection (optional)
sock: ?tcp = tcp.connect("localhost", 3306)Returns nil if the connection fails instead of panicking.
sock: ?tcp = tcp.connect("db.example.com", 3306)
if sock == nil {
print("Cannot reach database server")
return
}Connection options
The third argument is an options map with any combination of:
sock: tcp = tcp.connect("localhost", 3306, { timeout: 5 })
sock: tcp = tcp.connect("localhost", 3306, { timeout: 5, read_timeout: 10 })
sock: tcp = tcp.connect("db.example.com", 3307, { tls: true })
sock: tcp = tcp.connect("db.example.com", 3307, { tls: true, timeout: 5, read_timeout: 10 })| Option | Type | Default | Description |
|---|---|---|---|
timeout |
int |
system default | Connection timeout in seconds |
read_timeout |
int |
no timeout | Read timeout in seconds, applies to all reads |
tls |
bool |
false |
Perform TLS handshake immediately after connecting |
Options work with optional connections too:
sock: ?tcp = tcp.connect("slow-host.example.com", 3306, { timeout: 3 })TLS connections
When a service expects TLS from the start (HTTPS, SMTPS, MySQL with require_secure_transport), use the tls option:
sock: tcp = tcp.connect("smtp.gmail.com", 465, { tls: true })For protocols that start unencrypted and upgrade mid-conversation (MySQL, SMTP, LDAP), see start_tls() below.
Writing data
sock.write(data)The type of the argument determines what happens:
[byte]— sends the raw bytesstr— converts to bytes and sends
// Binary data — Luma sees [byte], sends bytes
header: [byte] = buf.to_bytes()
sock.write(header)
// Text command — Luma sees str, sends the string
sock.write("QUIT\r\n")Writing to a closed or broken connection panics.
Reading data
Read exact bytes
data: [byte] = sock.read(4)Blocks until exactly N bytes have been received.
This is the primary mode for protocol work where packet sizes are known.
The declared type controls the result format:
data: [byte] = sock.read(4) // 4 bytes as byte list
text: str = sock.read(1024) // 1024 bytes as stringUse optional types for safe reads:
data: ?[byte] = sock.read(4) // nil on error instead of panic
text: ?str = sock.read(1024) // nil on error instead of panicRead a line
line: str = sock.read_line()Reads until \n is encountered.
Used for text-based protocols (SMTP, HTTP headers, Redis).
line: str = sock.read_line() // panics if connection drops
line: ?str = sock.read_line() // returns nil if connection dropsRead timeout
The read timeout is set once at connection time and applies to all reads:
sock: tcp = tcp.connect("host", 3306, { read_timeout: 5 })
data: [byte] = sock.read(4) // panics if not received within 5s
data: ?[byte] = sock.read(4) // returns nil on timeoutClosing
sock.close()Closes the TCP connection.
Safe to call multiple times (second call is a no-op).
After closing, any read or write will panic.
TLS upgrade
Some protocols start unencrypted and upgrade to TLS mid-conversation:
sock: tcp = tcp.connect("localhost", 3306)
// ... unencrypted handshake ...
sock.start_tls()
// ... now encrypted ...Panics if the TLS handshake fails.
After this call, all reads and writes go through the encrypted channel.
This is different from { tls: true } in connect options — start_tls() upgrades an existing connection, while tls: true encrypts from the start.
Error handling
Consistent with Luma’s file I/O conventions:
| Operation | Non-optional | Optional |
|---|---|---|
tcp.connect(h, p) |
Panics on failure | ?tcp returns nil |
sock.read(n) |
Panics on error/timeout | ?[byte] or ?str returns nil |
sock.read_line() |
Panics on error | ?str returns nil |
sock.write(data) |
Panics on error | No optional variant |
sock.close() |
No-op if already closed | N/A |
sock.start_tls() |
Panics on failure | N/A |
Examples
Read a MySQL server greeting
sock: tcp = tcp.connect("localhost", 3306)
// MySQL sends a greeting packet immediately after connection
// Packet format: [3 bytes length][1 byte seq][payload]
header: [byte] = sock.read(4)
length: int = header[0] + header[1] * 256 + header[2] * 65536
seq: int = header[3]
payload: [byte] = sock.read(length)
print("Protocol version: ${payload[0]}")
sock.close()Simple HTTP client
sock: tcp = tcp.connect("example.com", 80)
sock.write("GET / HTTP/1.1\r\n")
sock.write("Host: example.com\r\n")
sock.write("Connection: close\r\n")
sock.write("\r\n")
status: str = sock.read_line()
print("Status: ${status}")
sock.close()Safe connection with fallback
sock: ?tcp = tcp.connect("primary.example.com", 5432, { timeout: 3 })
if sock == nil {
print("Primary unreachable, trying backup...")
sock = tcp.connect("backup.example.com", 5432)
}
data: [byte] = sock.read(1024)
sock.close()TLS connection
sock: tcp = tcp.connect("smtp.gmail.com", 465, { tls: true })
greeting: str = sock.read_line()
print("Server says: ${greeting}")
sock.write("EHLO localhost\r\n")
response: str = sock.read_line()
print("EHLO response: ${response}")
sock.close()Binary protocol with timeouts
sock: tcp = tcp.connect("gameserver.local", 9999, { read_timeout: 10 })
// Send a ping packet
sock.write([0x01, 0x00, 0x00, 0x04])
// Read response — nil if server doesn't respond within 10s
response: ?[byte] = sock.read(4)
if response == nil {
print("Server timed out")
sock.close()
return
}
print("Got response: ${response}")
sock.close()Method summary
| Method | Arguments | Returns | Description |
|---|---|---|---|
tcp.connect() |
host: str, port: int |
tcp or ?tcp |
Connect to a server |
tcp.connect() |
host: str, port: int, options |
tcp or ?tcp |
Connect with options |
sock.write() |
data: [byte] or data: str |
— | Send data |
sock.read() |
n: int |
[byte], str, ?[byte], or ?str |
Read exact N bytes |
sock.read_line() |
— | str or ?str |
Read until newline |
sock.close() |
— | — | Close connection |
sock.start_tls() |
— | — | Upgrade to TLS |
Design notes
- TCP connections are active values —
tcp.connect()connects immediately, unlikefile()which is lazy write()dispatches by argument type — pass bytes or a string, Luma figures it outread()dispatches by return type — declare what you want, Luma gives it to you- Options (
timeout,read_timeout,tls) are set once at connect time, keeping protocol code clean start_tls()exists separately because mid-conversation TLS upgrade is a genuinely different operation from connecting with TLS enabled