Module net/tcp

net/tcp
Stability: unstable — two shapes are still open, both in the READ direction. A `TcpStream` can be written from safe code (`write_str`, `write_string`, `write_bytes`) but can only be read through `read(buf : *(u8), size, io)`, so a program without `pragma(Pragma.AllowUnsafe)` has to go through the `Reader` defaults or `std/io/bufio`'s `BufReader` to get a byte out — the same asymmetry `std/net/udp` closed on 2026-09-09 with `recv_bytes`, and not yet closed here. And there is no `incoming`: Rust's listener iterator needs the async iterator protocol Yo does not have (`std/io/index.yo` names the same gap), so `accept` in a loop is the only spelling. Freezing waits on a pointer-free read; the listener/stream names, which mirror Rust's, are not expected to move. — stable modules only change additively; this one may still change.

Async TCP networking — typed TcpListener and TcpStream objects over std/sys/tcp, with Exception-based error handling.

Every operation that touches the network is an io.async future, so it runs on the ONE event-loop thread: awaiting a read parks this task and lets every other task on the loop run, and nothing here blocks a thread. Kernel failures are thrown as IoExn carrying an IoError (D1) instead of being returned, which is why a resolved byte count is always a real count and never a negative errno.

{ TcpListener, TcpStream } :: import("std/net/tcp");
{ SocketAddr } :: import("std/net/addr");
{ Exception, IoExn } :: import("std/error");

main :: (fn(io : Io, exn : Exception) -> unit)({
  e := IoExn(io : io, exn : exn);
  listener := io.await(TcpListener.bind(SocketAddr.loopback(u16(8080)), io), e);
  stream := io.await(listener.accept(io), e);
  io.await(stream.write_str("pong", io), e);
});

Stability

unstable — two shapes are still open, both in the READ direction. A TcpStream can be written from safe code (write_str, write_string, write_bytes) but can only be read through read(buf : *(u8), size, io), so a program without pragma(Pragma.AllowUnsafe) has to go through the Reader defaults or std/io/bufio's BufReader to get a byte out — the same asymmetry std/net/udp closed on 2026-09-09 with recv_bytes, and not yet closed here. And there is no incoming: Rust's listener iterator needs the async iterator protocol Yo does not have (std/io/index.yo names the same gap), so accept in a loop is the only spelling. Freezing waits on a pointer-free read; the listener/stream names, which mirror Rust's, are not expected to move.

Types

Shutdown enum
Shutdown

Which half of a TCP connection to shut down — the argument to TcpStream.shutdown.

Variants

VariantFieldsDescription
Read

Further receives are disallowed (SHUT_RD).

Write

Further sends are disallowed (SHUT_WR).

Both

Both further sends and receives are disallowed (SHUT_RDWR).

Incoming object
Incoming

The stream of connections arriving at a TcpListener — Rust's TcpListener::incoming, created by listener.incoming().

Item is a Result, not a bare TcpStream, for the reason Rust's is: a single failed accept (a client that vanished between the SYN and the accept, a per-process descriptor limit) is ONE bad connection, not the end of the listener. Carrying the failure in the item is what lets the stream survive it — and it is why Stream.next needs no Exception handler (std/async/stream.yo).

The error is a NetError, the same type accept THROWS, so moving a server loop from accept to incoming does not change its error vocabulary.

The stream finishes (.None) once the listener is closed — including by another task while a next is pending — mirroring Watcher.

Fields

NameTypeDescription
_listenerTcpListener

Trait Implementations

impl(generic(S : Type), where(S <: Stream), S : (Stream))
map : fn(generic(A, B, F) self : S : (Stream), f : F : (Fn(A) -> B)) -> StreamMap(S : (Stream), B, F : (Fn(A) -> B))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> B)

Returns: StreamMap(S : (Stream), B, F : (Fn(A) -> B))

filter : fn(generic(A, F) self : S : (Stream), f : F : (Fn(A) -> bool)) -> StreamFilter(S : (Stream), F : (Fn(A) -> bool))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> bool)

Returns: StreamFilter(S : (Stream), F : (Fn(A) -> bool))

filter_map : fn(generic(A, B, F) self : S : (Stream), f : F : (Fn(A) -> Option(B))) -> StreamFilterMap(S : (Stream), B, F : (Fn(A) -> Option(B)))

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> Option(B))

Returns: StreamFilterMap(S : (Stream), B, F : (Fn(A) -> Option(B)))

take : fn(generic(A) self : S : (Stream), n : usize) -> StreamTake(S : (Stream))

Parameters

NameTypeNotes
selfS : (Stream)
nusize

Returns: StreamTake(S : (Stream))

skip : fn(generic(A) self : S : (Stream), n : usize) -> StreamSkip(S : (Stream))

Parameters

NameTypeNotes
selfS : (Stream)
nusize

Returns: StreamSkip(S : (Stream))

for_each : fn(generic(A, F) self : S : (Stream), f : F : (Fn(A) -> unit), io : Io) -> Impl : (Future[Future](unit) Io : Io)

Parameters

NameTypeNotes
selfS : (Stream)
fF : (Fn(A) -> unit)
ioIo

Returns: Impl : (Future[Future](unit) Io : Io)

collect : fn(generic(A) self : S : (Stream), io : Io) -> Impl : (Future[Future](ArrayList(A)) Io : Io)

Parameters

NameTypeNotes
selfS : (Stream)
ioIo

Returns: Impl : (Future[Future](ArrayList(A)) Io : Io)

impl(Incoming, Stream(...))
next : (Incoming) fn(self : Incoming, io : Io) -> Impl : (Future[Future](Option(Result(TcpStream, NetError))) Io : Io)

Yield the next item, or .None once the stream is finished.

Parameters

NameTypeNotes
selfIncoming
ioIo

Returns: Impl : (Future[Future](Option(Result(TcpStream, NetError))) Io : Io)

TcpListener object
TcpListener

A TCP socket that listens for incoming connections.

Fields

NameTypeDescription
_fdi32
_local_addrSocketAddr
_is_closedbool

Trait Implementations

impl(TcpListener, ...)
bind : (TcpListener) fn(addr : SocketAddr, io : Io) -> Impl : (Future[Future](TcpListener) IoExn : IoExn)

Bind to a socket address and start listening.

Parameters

NameTypeNotes
addrSocketAddr
ioIo

Returns: Impl : (Future[Future](TcpListener) IoExn : IoExn)

accept : (TcpListener) fn(self : TcpListener, io : Io) -> Impl : (Future[Future](TcpStream) IoExn : IoExn)

Accept an incoming connection, returning a new TcpStream.

Parameters

NameTypeNotes
selfTcpListener
ioIo

Returns: Impl : (Future[Future](TcpStream) IoExn : IoExn)

incoming : (TcpListener) fn(self : TcpListener) -> Incoming

The connections arriving at this listener, as a Stream of Result(TcpStream, NetError) — Rust's TcpListener::incoming.

conns := listener.incoming().take(usize(3));
io.await(conns.for_each(c => { handle(c); }, io), io);

Every std/async/stream combinator applies, and a function can be generic over where(S <: Stream) instead of over this listener.

Parameters

NameTypeNotes
selfTcpListener

Returns: Incoming

local_addr : (TcpListener) fn(self : TcpListener) -> SocketAddr

Get this end's socket address — Rust's TcpStream::local_addr. Read from the kernel once, when the stream was connected or accepted, because that is the only way to learn the ephemeral port and the source interface the kernel chose; neither is in the connect argument.

Parameters

NameTypeNotes
selfTcpListener

Returns: SocketAddr

close : (TcpListener) fn(self : TcpListener, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Close the stream.

Parameters

NameTypeNotes
selfTcpListener
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

fd : (TcpListener) fn(self : TcpListener) -> i32

Get the underlying file descriptor.

Parameters

NameTypeNotes
selfTcpListener

Returns: i32

impl(TcpListener, Dispose(...))
dispose : (TcpListener) fn(self : TcpListener) -> unit

Release the resources self owns — a file descriptor, a socket, a lock, a buffer the allocator handed out. Called automatically when the last reference to the value goes away, so an implementor never calls it directly and must tolerate being the only one who ever does.

It must be safe to run exactly once: the runtime calls it at refcount zero, and a type that also exposes an explicit close/release is responsible for making the second call a no-op.

Parameters

NameTypeNotes
selfTcpListener

Returns: unit

TcpStream object
TcpStream

A connected TCP stream for bidirectional data transfer.

Fields

NameTypeDescription
_fdi32
_peer_addrSocketAddr
_local_addrSocketAddr
_is_closedbool

Trait Implementations

Dispose IoTraits
impl(TcpStream, ...)
connect : (TcpStream) fn(addr : SocketAddr, io : Io) -> Impl : (Future[Future](TcpStream) IoExn : IoExn)

Connect to a remote address, returning a new TcpStream.

Parameters

NameTypeNotes
addrSocketAddr
ioIo

Returns: Impl : (Future[Future](TcpStream) IoExn : IoExn)

read : (TcpStream) fn(self : TcpStream, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Read up to size bytes from the stream into buf, resolving to the number actually read. 0 — and only 0 — means the peer closed its end; a SHORT count is ordinary on a stream socket and says nothing about end-of-stream, so a caller that needs size bytes loops. Awaiting parks this task on the event loop until the kernel has bytes; a failure throws IoExn rather than resolving to a negative count.

buf must have room for size bytes, and raw pointers are unavailable in safe code, so calling this directly needs pragma(Pragma.AllowUnsafe). Safe code uses the Reader defaults (read_to_end, read_to_string) or wraps the stream in BufReader(TcpStream).

Parameters

NameTypeNotes
selfTcpStream
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

write : (TcpStream) fn(self : TcpStream, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Write up to size raw bytes from buf, resolving to the number the kernel accepted — which may be LESS than size. A caller that must deliver everything loops, or takes the Writer trait's write_all. Awaiting parks this task until the socket is writable; a failure throws IoExn. Needs pragma(Pragma.AllowUnsafe) for the raw pointer — write_bytes / write_str / write_string are the safe spellings.

Parameters

NameTypeNotes
selfTcpStream
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

write_str : (TcpStream) fn(self : TcpStream, data : str, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Write a str to the stream. Returns the number of bytes written.

Parameters

NameTypeNotes
selfTcpStream
datastr
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

write_string : (TcpStream) fn(self : TcpStream, data : String, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Write a String to the stream. Returns the number of bytes written.

Parameters

NameTypeNotes
selfTcpStream
dataString
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

write_bytes : (TcpStream) fn(self : TcpStream, data : ArrayList(u8), io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Write raw bytes from an ArrayList(u8) to the stream. Returns the number of bytes written.

Parameters

NameTypeNotes
selfTcpStream
dataArrayList(u8)
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

shutdown : (TcpStream) fn(self : TcpStream, how : Shutdown, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Shut down part or all of the connection.

Parameters

NameTypeNotes
selfTcpStream
howShutdown
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

close : (TcpStream) fn(self : TcpStream, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Close the stream.

Parameters

NameTypeNotes
selfTcpStream
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

peer_addr : (TcpStream) fn(self : TcpStream) -> SocketAddr

Get the remote peer's socket address.

Parameters

NameTypeNotes
selfTcpStream

Returns: SocketAddr

local_addr : (TcpStream) fn(self : TcpStream) -> SocketAddr

Get this end's socket address — Rust's TcpStream::local_addr. Read from the kernel once, when the stream was connected or accepted, because that is the only way to learn the ephemeral port and the source interface the kernel chose; neither is in the connect argument.

Parameters

NameTypeNotes
selfTcpStream

Returns: SocketAddr

fd : (TcpStream) fn(self : TcpStream) -> i32

Get the underlying file descriptor.

Parameters

NameTypeNotes
selfTcpStream

Returns: i32

set_nodelay : (TcpStream) fn(self : TcpStream, nodelay : bool, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Enable or disable TCP_NODELAY (disables Nagle's algorithm).

Parameters

NameTypeNotes
selfTcpStream
nodelaybool
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

set_keepalive : (TcpStream) fn(self : TcpStream, enabled : bool, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Enable or disable SO_KEEPALIVE on the socket.

Parameters

NameTypeNotes
selfTcpStream
enabledbool
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

impl(TcpStream, Dispose(...))
dispose : (TcpStream) fn(self : TcpStream) -> unit

Release the resources self owns — a file descriptor, a socket, a lock, a buffer the allocator handed out. Called automatically when the last reference to the value goes away, so an implementor never calls it directly and must tolerate being the only one who ever does.

It must be safe to run exactly once: the runtime calls it at refcount zero, and a type that also exposes an explicit close/release is responsible for making the second call a no-op.

Parameters

NameTypeNotes
selfTcpStream

Returns: unit

impl(TcpStream, IoTraits)
impl(TcpStream, IoTraits)
Methods
read_to_end : (TcpStream) fn(self : TcpStream, io : Io) -> Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)

Parameters

NameTypeNotes
selfTcpStream
ioIo

Returns: Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)

read_to_string : (TcpStream) fn(self : TcpStream, io : Io) -> Impl : (Future[Future](String) IoExn : IoExn)

Parameters

NameTypeNotes
selfTcpStream
ioIo

Returns: Impl : (Future[Future](String) IoExn : IoExn)

flush : (TcpStream) fn(self : TcpStream, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Parameters

NameTypeNotes
selfTcpStream
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

write_all : (TcpStream) fn(self : TcpStream, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Parameters

NameTypeNotes
selfTcpStream
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

Functions

make_sockaddr function
fn(addr : SocketAddr) -> IO_tcp.SockAddr

Convert a SocketAddr to a low-level SockAddr buffer.

Shared with std/net/udp, which used to carry a byte-for-byte COPY of this — which is how the hardcoded-"::1" bug below had to be fixed twice. One encoder and one decoder (sockaddr_to_socket_addr) for the whole net layer.

Parameters

NameTypeNotes
addrSocketAddr

Returns: IO_tcp.SockAddr

fn(buf : *u8) -> SocketAddr

Parse a low-level sockaddr buffer (as filled in by accept/getsockname/ recvfrom) back into a SocketAddr. Unknown families fall back to 0.0.0.0:0. Shared with std/net/udp (its bind readback) — one decoder for the whole net layer.

Parameters

NameTypeNotes
buf*u8

Returns: SocketAddr