3.2. net:: - networking (TCP / UDP / Unix + TLS 1.3)
The rok::std::net module provides low-level network primitives together with TLS 1.3 as one bundle. High-level protocols such as HTTP, HTTP/2 and HTTP/3 are deliberately out of scope and delegated to separate packages such as rok/http. This choice also avoids Go's path, where the standard library froze at 1.0 and stopped evolving.
3.2.0. Common rules
- Data being written (
write/write_all/send/send_toand their_asyncforms) acceptsstr,List<u8>andSlice<u8>throughAsBytes(3.5.1). The read buffer is aList<u8>.read(buf)fills the firstnbytes of abufwhose length was set up in advance and returnsn.0is the end of the stream. - Socket types are OS handles. Copying a value shares the same socket; only
try_cloneduplicates the fd. - Sockets implement
Drop, so they are closed on leaving ausingblock (2.13.4). Otherwise, close them withclose(). Using a closed socket is anIoError(InvalidInput). A*_asyncoperation pending on the socket when another task closes it completes with that error; aftershutdown(Read | Both)a pending read completes with 0. TcpStreamimplementsReader+Writer(3.5.1).
Blocking and *_async
Methods come in two sets.
| Form | How it waits |
|---|---|
accept, read, write .. | Blocks the calling OS thread (and, inside a task, its worker) |
accept_async, read_async .. | Suspends only the task; the worker runs other tasks |
*_async methods are async fns, used as try await s.read_async(buf). Waiting for readiness is the job of the I/O driver. An ordinary function cannot suspend a stackless coroutine, so the sync call of the same name does not suspend on its own inside a task.
| blocking | async |
|---|---|
TcpListener::accept | TcpListener::accept_async |
TcpStream::connect | TcpStream::connect_async |
TcpStream::read | TcpStream::read_async |
TcpStream::read_to_end | TcpStream::read_to_end_async |
TcpStream::write | TcpStream::write_async |
TcpStream::write_all | TcpStream::write_all_async |
TcpStream::write_str | TcpStream::write_str_async |
UdpSocket::send_to | UdpSocket::send_to_async |
UdpSocket::recv_from | UdpSocket::recv_from_async |
UdpSocket::send | UdpSocket::send_async |
UdpSocket::recv | UdpSocket::recv_async |
tcp_connect | tcp_connect_async |
tls::Client::new / with_config | tls::Client::new_async / with_config_async |
tls::Server::new | tls::Server::new_async |
tls::Client::read ... key_update (and Server) | read_async ... key_update_async |
Timeouts apply only to the blocking side (set_read_timeout / set_write_timeout; exceeding them gives TimedOut). On the async side, wrap with task::timeout (waits on a task) or task::select (2.18.1). select cancels the losing side, which makes it a good fit for abandoning a read.
enum Got { Data(usize), Timeout }
let (_, got) = await task::select([
async { Got::Data(try await s.read_async(buf) catch _ { 0 }) },
async { await task::sleep(Duration::sec(5)); Got::Timeout },
]);
With set_nonblocking(true), blocking methods do not wait and throw WouldBlock.
On wasm32-wasi, std::net (TCP / UDP connect, bind, listen, accept, DNS) works through wasi:sockets; the host must grant network access (e.g. wasmtime -S inherit-network -S allow-ip-name-lookup). On wasm32-wasi-threads only TcpListener::inherited() listeners work; connect / bind / DNS throw Unsupported.
3.2.1. TCP
TCP is built around two types, TcpListener and TcpStream. The listener handles bind and the accept flow; the stream exposes reading, writing and option control.
struct TcpListener { /* OS handle */ }
struct TcpStream { /* OS handle */ }
impl TcpListener {
fn bind(addr: SocketAddr) throws (IoError) -> TcpListener; // SO_REUSEADDR, backlog 128
fn inherited() -> List<TcpListener>; // listeners the host hands to the process
fn accept(self) throws (IoError) -> (TcpStream, SocketAddr);
async fn accept_async(self) throws (IoError) -> (TcpStream, SocketAddr);
fn local_addr(self) throws (IoError) -> SocketAddr; // the actual port after binding to port 0
fn try_clone(self) throws (IoError) -> TcpListener;
fn set_nonblocking(self, nb: bool) throws (IoError);
fn incoming(self) -> Incoming; // implements Iterator<Item = Result<TcpStream, IoError>>
fn fd(self) -> i32;
fn close(self);
}
impl TcpStream {
fn connect(addr: SocketAddr) throws (IoError) -> TcpStream;
fn connect_timeout(addr: SocketAddr, dur: Duration) throws (IoError) -> TcpStream;
async fn connect_async(addr: SocketAddr) throws (IoError) -> TcpStream;
fn read(self, buf: List<u8>) throws (IoError) -> usize;
fn read_to_end(self) throws (IoError) -> List<u8>; // to the end
fn write<B: AsBytes>(self, buf: B) throws (IoError) -> usize;
fn write_all<B: AsBytes>(self, buf: B) throws (IoError);
fn write_str(self, s: str) throws (IoError);
fn flush(self) throws (IoError); // no-op (no buffer)
// + read_async / read_to_end_async / write_async / write_all_async / write_str_async
fn peer_addr(self) throws (IoError) -> SocketAddr;
fn local_addr(self) throws (IoError) -> SocketAddr;
fn shutdown(self, how: Shutdown) throws (IoError);
fn try_clone(self) throws (IoError) -> TcpStream;
fn fd(self) -> i32;
fn close(self);
// TCP options
fn set_nodelay(self, nodelay: bool) throws (IoError); // TCP_NODELAY
fn set_keepalive(self, alive: Duration | none) throws (IoError); // SO_KEEPALIVE + idle (seconds), none = off
fn set_read_timeout(self, dur: Duration | none) throws (IoError);
fn set_write_timeout(self, dur: Duration | none) throws (IoError);
fn read_timeout(self) -> Duration | none;
fn write_timeout(self) -> Duration | none;
fn set_nonblocking(self, nb: bool) throws (IoError);
}
impl Reader for TcpStream { ... }
impl Writer for TcpStream { ... }
impl Drop for TcpStream { ... } // close
impl Drop for TcpListener { ... }
enum Shutdown { Read, Write, Both }
TcpListener::inherited() returns the listeners the host hands to the process: WASI pre-opened sockets, systemd LISTEN_FDS socket activation on Unix, and none on Windows.
Incoming never ends. Each element calls accept once, and its errors come out as Err elements. If the peer closes during a write, the process is not terminated by SIGPIPE; the write yields an IoError (BrokenPipe).
Examples
use rok::std::net::{tcp_listen, TcpListener, TcpStream};
// blocking - this thread waits in accept and handle
using l = try tcp_listen("127.0.0.1:8080") {
for c in l.incoming() {
handle(try c.get());
}
}
// async - one task per connection; while waiting, the worker runs other tasks
async fn serve_all(l: TcpListener) throws (IoError) {
loop {
let (s, peer) = try await l.accept_async();
task::spawn(serve(s)); // async fn serve(s: TcpStream)
}
}
3.2.2. UDP
For UDP, a single UdpSocket type covers every role. Datagram send/receive is the baseline, and once a peer is fixed with connect, send / recv alone suffice. Options such as multicast membership and TTL are exposed too, and with workloads like QUIC in mind it includes segment size control for GSO/GRO.
struct UdpSocket { /* OS handle */ }
impl UdpSocket {
fn bind(addr: SocketAddr) throws (IoError) -> UdpSocket;
fn send_to<B: AsBytes>(self, buf: B, addr: SocketAddr) throws (IoError) -> usize;
fn recv_from(self, buf: List<u8>) throws (IoError) -> (usize, SocketAddr);
fn connect(self, addr: SocketAddr) throws (IoError); // fix the peer
fn send<B: AsBytes>(self, buf: B) throws (IoError) -> usize;
fn recv(self, buf: List<u8>) throws (IoError) -> usize;
// + send_to_async / recv_from_async / send_async / recv_async
fn local_addr(self) throws (IoError) -> SocketAddr;
fn peer_addr(self) throws (IoError) -> SocketAddr;
fn set_broadcast(self, on: bool) throws (IoError); // SO_BROADCAST
fn set_read_timeout(self, dur: Duration | none) throws (IoError);
fn set_write_timeout(self, dur: Duration | none) throws (IoError);
fn set_nonblocking(self, nb: bool) throws (IoError);
fn try_clone(self) throws (IoError) -> UdpSocket;
fn fd(self) -> i32;
fn close(self);
// multicast (IPv4)
fn join_multicast_v4(self, addr: Ipv4Addr, iface: Ipv4Addr) throws (IoError);
fn leave_multicast_v4(self, addr: Ipv4Addr, iface: Ipv4Addr) throws (IoError);
fn set_multicast_loop_v4(self, on: bool) throws (IoError);
fn set_multicast_ttl_v4(self, ttl: u32) throws (IoError);
// for QUIC (GSO / GRO)
fn set_segment_size(self, size: u16) throws (IoError); // UDP_SEGMENT, Unsupported outside Linux
}
impl Drop for UdpSocket { ... } // close
recv_from / recv receive a single datagram into buf. The remainder of a datagram longer than buf is discarded.
3.2.3. Unix domain socket
Unix domain sockets come in three types: UnixListener, UnixStream and UnixDatagram. Their overall shape resembles TCP, but since they communicate only within the same host they use file paths as identifiers, and they provide convenience methods such as pair(), corresponding to socketpair(2). Fd passing through SCM_RIGHTS is meaningful only on Unix sockets, so it is split out into send_with_fd / recv_with_fd.
struct UnixListener { /* unix only */ }
struct UnixStream { /* unix only */ }
struct UnixDatagram { /* unix only */ }
#[cfg(target_family = "unix")]
impl UnixListener {
fn bind(path: Path) throws (IoError) -> UnixListener;
fn accept(self) throws (IoError) -> (UnixStream, Path | none); // peer's bound path
// ... similar to TcpListener
}
#[cfg(target_family = "unix")]
impl UnixStream {
fn connect(path: Path) throws (IoError) -> UnixStream;
fn pair() throws (IoError) -> (UnixStream, UnixStream); // socketpair
// SCM_RIGHTS - fd passing
fn send_with_fd(self, buf: List<u8>, fd: RawFd) throws (IoError) -> usize;
fn recv_with_fd(self, buf: List<u8>) throws (IoError) -> (usize, RawFd | none);
}
3.2.4. SocketAddr / IpAddr
Address representation centers on the SocketAddr enum, the union of IPv4 and IPv6. Each variant exposes the IP and port, and for IPv6 also the flow info and scope id, so they can be inspected directly when needed. String parsing works through FromStr implementations, and DNS lookup of host names is provided by separate functions.
enum SocketAddr {
V4(SocketAddrV4),
V6(SocketAddrV6),
}
struct SocketAddrV4 { ip: Ipv4Addr; port: u16; }
struct SocketAddrV6 { ip: Ipv6Addr; port: u16; flow_info: u32; scope_id: u32; }
impl SocketAddr {
fn v4(ip: Ipv4Addr, port: u16) -> SocketAddr;
fn v6(ip: Ipv6Addr, port: u16) -> SocketAddr; // flow_info = scope_id = 0
fn port(self) -> u16;
fn with_port(self, port: u16) -> SocketAddr;
fn is_ipv4(self) -> bool;
fn is_ipv6(self) -> bool;
}
struct Ipv4Addr { octets: [u8; 4]; }
struct Ipv6Addr { segments: [u16; 8]; }
impl Ipv4Addr {
const LOCALHOST: Ipv4Addr = Ipv4Addr { octets: [127, 0, 0, 1] };
const UNSPECIFIED: Ipv4Addr = Ipv4Addr { octets: [0, 0, 0, 0] };
fn new(a: u8, b: u8, c: u8, d: u8) -> Ipv4Addr;
fn to_bits(self) -> u32;
fn is_loopback(self) -> bool;
fn is_unspecified(self) -> bool;
fn is_multicast(self) -> bool;
}
impl Ipv6Addr {
const LOCALHOST: Ipv6Addr; // ::1
const UNSPECIFIED: Ipv6Addr; // ::
fn new(a: u16, b: u16, c: u16, d: u16, e: u16, f: u16, g: u16, h: u16) -> Ipv6Addr;
fn is_loopback(self) -> bool;
}
// parsing / printing - all three types implement FromStr (Err = ParseError), Display, Debug, PartialEq
impl FromStr for SocketAddr { ... } // "127.0.0.1:8080" / "[::1]:8080"
impl FromStr for Ipv4Addr { ... }
impl FromStr for Ipv6Addr { ... } // "::" abbreviation allowed
// DNS (getaddrinfo)
fn resolve(host: str) throws (IoError) -> List<SocketAddr>;
fn resolve_one(host: str, port: u16) throws (IoError) -> SocketAddr;
resolve obtains a list of addresses with the OS's getaddrinfo and blocks the calling thread. host accepts:
host | Result |
|---|---|
A socket address ("10.0.0.1:80", "[::1]:80") | That single address (no lookup) |
"name:port" | The addresses of name, with port port |
"[v6]:port" | That IPv6 address, with port port |
| A name or IP only | Its addresses, with port 0 |
resolve_one takes the name and port separately and returns the first address. If the name is not found it is an IoError (NotFound), and the message includes the resolver's description.
3.2.5. TLS 1.3 - net::tls
TLS lives in the net::tls submodule and assumes 1.3 only from the start. ClientConfig and ServerConfig express certificates, cipher suites and the ALPN protocol list, and the post-quantum option enables ML-KEM hybrid key exchange. After the handshake, Client / Server behave like plaintext streams, so existing I/O code can sit on top almost unchanged.
use rok::std::net::tls;
struct ClientConfig {
root_certs: List<Certificate>; // trust anchors
cipher_suites: List<CipherSuite>; // TLS 1.3 suites, preference order
alpn_protocols: List<List<u8>>; // ["h2", "http/1.1"], etc.
enable_pq: bool; // post-quantum (X25519MLKEM768 hybrid)
client_cert_chain: List<Certificate>; // sent when the server asks: leaf first; empty: none
client_key: PrivateKey | none; // the client leaf's key
session_cache: SessionCache | none; // tickets to resume from; none: never resume
early_data: List<u8>; // 0-RTT data, else the first data after the handshake
}
impl ClientConfig {
fn new() -> ClientConfig; // system_roots(), all suites, no ALPN, enable_pq = true,
// no client certificate, SessionCache::new(), no early data
}
struct ServerConfig {
cert_chain: List<Certificate>; // leaf first, then intermediates
private_key: PrivateKey; // the leaf's key
cipher_suites: List<CipherSuite>;
alpn_protocols: List<List<u8>>;
client_roots: List<Certificate>; // non-empty: ask for a client certificate, validated against these
require_client_cert: bool; // with client_roots: refuse a client without one
ocsp_response: List<u8>; // DER OCSPResponse stapled when asked; empty: none
ticket_store: TicketStore | none; // resumption; none: no tickets
max_early_data: u32; // 0-RTT bytes accepted per connection; 0: none
}
impl ServerConfig {
fn new(cert_chain: List<Certificate>, private_key: PrivateKey) throws (TlsError) -> ServerConfig;
// the key must belong to cert_chain[0]; all suites, no ALPN, no client certificates,
// no OCSP response, TicketStore::new(), require_client_cert = true, max_early_data = 0
}
struct SessionCache { // the client's tickets by server name; copies share them
fn new() -> SessionCache;
fn len(self) -> usize;
fn clear(self);
}
struct TicketStore { // the server's resumable sessions; copies share them
fn new() -> TicketStore;
fn len(self) -> usize;
}
enum CipherSuite { Aes128GcmSha256, Aes256GcmSha384, ChaCha20Poly1305Sha256 }
struct Certificate {
fn from_der(der: List<u8> | Slice<u8>) throws (TlsError) -> Certificate;
fn from_pem(text: str) throws (TlsError) -> List<Certificate>;
fn der(self) -> List<u8>;
fn dns_names(self) -> List<str>;
fn not_before(self) -> i64; // Unix seconds
fn not_after(self) -> i64;
}
struct PrivateKey { // Ed25519, ECDSA P-256 / P-384, RSA (2048..4096 bits), RSASSA-PSS
fn from_der(der: List<u8> | Slice<u8>) throws (TlsError) -> PrivateKey; // PKCS #8
fn from_pem(text: str) throws (TlsError) -> PrivateKey; // PRIVATE KEY, EC PRIVATE KEY or RSA PRIVATE KEY
}
fn system_roots() throws (TlsError) -> List<Certificate>; // the OS trust store (read on each call)
enum TlsErrorKind { Io, Certificate, Protocol, PeerAlert }
struct TlsError { kind: TlsErrorKind; msg: str; alert: u8; }
struct Client {
fn new(stream: TcpStream, server_name: str) throws (TlsError) -> Client;
fn with_config(stream: TcpStream, server_name: str, cfg: ClientConfig) throws (TlsError) -> Client;
async fn new_async(stream: TcpStream, server_name: str) throws (TlsError) -> Client;
async fn with_config_async(stream: TcpStream, server_name: str, cfg: ClientConfig) throws (TlsError) -> Client;
fn read(self, buf: List<u8>) throws (IoError) -> usize;
fn read_to_end(self) throws (IoError) -> List<u8>;
fn write<B: AsBytes>(self, buf: B) throws (IoError) -> usize;
fn write_all<B: AsBytes>(self, buf: B) throws (IoError);
fn flush(self) throws (IoError);
fn close_notify(self) throws (IoError);
fn key_update(self) throws (IoError);
fn close(self);
async fn read_async(self, buf: List<u8>) throws (IoError) -> usize;
async fn read_to_end_async(self) throws (IoError) -> List<u8>;
async fn write_async<B: AsBytes>(self, buf: B) throws (IoError) -> usize;
async fn write_all_async<B: AsBytes>(self, buf: B) throws (IoError);
async fn flush_async(self) throws (IoError);
async fn close_notify_async(self) throws (IoError);
async fn key_update_async(self) throws (IoError);
fn negotiated_alpn(self) -> List<u8> | none;
fn peer_certificates(self) -> List<Certificate>;
fn resumed(self) -> bool; // the handshake resumed a session
fn early_data_accepted(self) -> bool; // early_data went out as 0-RTT data
}
struct Server {
fn new(stream: TcpStream, cfg: ServerConfig) throws (TlsError) -> Server;
async fn new_async(stream: TcpStream, cfg: ServerConfig) throws (TlsError) -> Server;
// read, read_to_end, write, write_all, flush, close_notify, key_update,
// close, their *_async forms, negotiated_alpn and peer_certificates
// as on Client, and:
fn server_name(self) -> str | none; // the client's SNI
fn resumed(self) -> bool;
fn early_data(self) -> List<u8>; // the 0-RTT data accepted; read returns what follows
}
Client and Server implement Reader and Writer (3.5.1) and Drop (closing the socket after a close_notify). Copies share one connection; a connection is used by one thread at a time (!Sync) and by one operation at a time.
The *_async methods follow 3.2.0: they suspend only the task while the socket is not ready, and the handshake (new_async, with_config_async) does the same between the peer's messages. The blocking and async forms may be mixed on one connection.
The stack is implementation-provided and written in rok on rok::core::crypto (2.21): no operating-system or third-party TLS library is used, and users never call one. Only TLS 1.3 (RFC 8446) is spoken; TLS 1.2 and earlier are refused. The handshake supports X25519 and the X25519MLKEM768 hybrid (ML-KEM-768 + X25519, enable_pq), HelloRetryRequest (for a group or a cookie), ALPN, SNI, KeyUpdate, close_notify, client certificates, OCSP stapling, and resumption with 0-RTT data.
Resumption: a server with a ticket_store sends one NewSessionTicket after each handshake; the store keeps the session (at most 4096, the oldest dropped; lifetime 2 hours) and redeems a ticket at most once. A client with a session_cache keeps the tickets it receives under the server name it connected to (at most 64), and offers the newest live one whose cipher suite's hash it also offers, once (psk_dhe_ke only: the resumed handshake still does a fresh key exchange). A resumed connection skips certificate authentication; peer_certificates() returns the original connection's chain. A ticket is received while the client reads.
0-RTT: when early_data is non-empty and the ticket allows that many bytes, the client sends it as early data with the ClientHello; otherwise, or if the server declines, it is sent as the first application data after the handshake (early_data_accepted() tells which). A server accepts early data only with max_early_data > 0, on the first offered ticket, without HelloRetryRequest, with the ticket's cipher suite and ALPN protocol, and when the client's ticket age is within 10 seconds of the server's; it hands the data out through early_data() and skips early data it declines. Since every ticket is redeemed once from its TicketStore, a replayed ClientHello resumes nothing and its early data is skipped (RFC 8446 8.1); servers that should reject each other's replays must share one store. Early data is still not forward-secret with respect to the ticket and should be idempotent.
Client certificates: a server with non-empty client_roots sends a CertificateRequest; the client's chain is validated against client_roots like a server's (below) with extendedKeyUsage clientAuth and no name check, and its CertificateVerify checked. A client without a certificate fails the handshake with certificate_required if require_client_cert, else continues with no peer_certificates(). A client answers a CertificateRequest with client_cert_chain and a CertificateVerify by client_key when the server accepts a scheme for that key, else with an empty Certificate. A client_key that does not belong to the first certificate makes Client::with_config fail before anything is sent.
Server authentication: the certificate path is built from the peer's chain to one of root_certs and validated per RFC 5280 (signatures, validity at the current time, basicConstraints, keyUsage, extendedKeyUsage serverAuth, DNS and IP name constraints; unknown critical extensions fail). The server name is matched against subjectAltName only (RFC 6125: case-insensitive, a wildcard is the whole leftmost label); an IP literal is matched against iPAddress entries and sends no SNI. Accepted signatures: Ed25519, ECDSA P-256 / P-384 with SHA-256/384/512, RSA PKCS #1 v1.5 (certificates only) and RSA-PSS with SHA-256/384/512 (2048-4096-bit keys, rsaEncryption or id-RSASSA-PSS; in certificates any salt length, honoring an id-RSASSA-PSS key's hash and minimum salt). Revocation: the client asks for an OCSP response (status_request). A stapled response must be a successful basic OCSP response (RFC 6960) signed by the leaf's issuer or by a certificate the issuer issued with extendedKeyUsage OCSPSigning, with a SHA-1 or SHA-2 CertID naming the leaf, thisUpdate not more than 5 minutes ahead and nextUpdate present and not past; status revoked fails with certificate_revoked, any other problem (unknown status included) with bad_certificate_status_response. A leaf with the TLS feature extension for status_request (RFC 7633) requires a stapled response. Without a staple and without that extension the certificate is accepted; CRLs and OCSP queries over the network are not used.
A certificate that is expired or not yet valid fails with certificate_expired, one whose extendedKeyUsage does not allow the role with unsupported_certificate, one with no path to root_certs with unknown_ca, and any other failure with bad_certificate. An extension in ServerHello, HelloRetryRequest, EncryptedExtensions or a Certificate entry that the client did not request fails with unsupported_extension. A failed handshake sends its alert, then closes the stream.
Either side signs its CertificateVerify with its key's scheme: ed25519, ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, for an rsaEncryption certificate the first of rsa_pss_rsae_sha256 / _sha384 / _sha512 the peer offers, and for an id-RSASSA-PSS certificate the first of rsa_pss_pss_sha256 / _sha384 / _sha512 the peer offers that the key's parameters allow. PrivateKey reads PKCS #8 (Ed25519, id-ecPublicKey on P-256 / P-384, rsaEncryption, id-RSASSA-PSS), RFC 5915 EC PRIVATE KEY and PKCS #1 RSA PRIVATE KEY. ServerConfig::new fails (TlsErrorKind::Certificate) when the key's public half is not the leaf's.
The stack keeps its traffic, handshake and resumption secrets, record keys, key-exchange secrets, ticket PSKs and Ed25519 / EC private keys in secret storage (2.21.5), and wipes them on KeyUpdate, at the end of the handshake and on close.
system_roots() reads the OS trust store: the macOS keychain anchors (plus admin / user certificates trusted unconditionally), the Windows ROOT store, the first CA bundle found on Linux (/etc/ssl/certs/ca-certificates.crt, /etc/pki/tls/certs/ca-bundle.crt, ...). SSL_CERT_FILE overrides it. wasm targets have no store: pass root_certs.
read returns 0 after the peer's close_notify; a connection that ends without one is an IoError (UnexpectedEof); a protocol failure is an IoError (InvalidData) after a fatal alert is sent. TLS records are written whole, so write writes all of buf.
Example
Actual use is simple: put a TLS client on top of a plaintext TCP connection and call plaintext I/O on it.
use rok::std::net::{tcp_connect, tls};
let conn = try tcp_connect("example.com:443");
let tls_conn = try tls::Client::new(conn, "example.com");
try tls_conn.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
let resp = try tls_conn.read_to_end();
try tls_conn.close_notify();
3.2.6. Convenience functions
Common patterns are wrapped in short helpers. Forms that take a string perform DNS resolution with resolve (3.2.4), so a host name can be passed directly, as in tcp_connect("example.com:80").
fn tcp_connect(addr: str | SocketAddr) throws (IoError) -> TcpStream;
async fn tcp_connect_async(addr: str | SocketAddr) throws (IoError) -> TcpStream;
fn tcp_listen(addr: str | SocketAddr) throws (IoError) -> TcpListener;
fn udp_bind(addr: str | SocketAddr) throws (IoError) -> UdpSocket;
tcp_connect tries the resolved addresses in order and returns the first that connects; if all fail, it throws the last error. tcp_listen / udp_bind bind to the first address.
3.2.7. zerocopy hooks (advanced)
For places where throughput is cost, such as proxies or file servers, zerocopy hooks are exposed separately. Most users do not need them; the goal is only to keep them within reach when they really are needed.
impl TcpStream {
fn sendfile(self, ref file: File, offset: u64, count: u64) throws (IoError) -> u64;
// sendfile(2) / TransmitFile
fn splice(self, ref src: File, count: u64) throws (IoError) -> u64;
// Linux splice(2)
fn write_zerocopy(self, buf: List<u8>) throws (IoError) -> usize;
// MSG_ZEROCOPY (Linux 4.14+)
}
3.2.8. Module mapping
Per-platform support is summarized below. Core features work on both desktop OSes and wasi, but items that depend on Linux-specific facilities, such as Unix sockets and zerocopy hooks, are enabled only in limited places.
| Item | linux/macos/windows | wasi | Notes |
|---|---|---|---|
| TCP | ✓ | ✓ | wasi:sockets |
| UDP | ✓ | ✓ | wasi:sockets |
| Unix socket | linux/macos | ✗ | Windows partial (named pipes separate) |
| TLS 1.3 | ✓ | ✓ (no trust store: pass root_certs) | rok's own, on rok::core::crypto |
| sendfile | linux | ✗ | |
| splice | linux | ✗ | |
| MSG_ZEROCOPY | linux 4.14+ | ✗ |