Module net/udp

net/udp
Stability: unstable — the receive surface was rebuilt on 2026-09-09 (`recv_from` now resolves to `(usize, SocketAddr)` instead of taking two C out-params, and `recv_bytes` / `recv_from_bytes` gave safe code a way to read a datagram at all), so it is inside its one-release window. The open question is whether the raw pointer pair (`recv`, `recv_from`) stays exported beside the pointer-free pair: two spellings per operation is what D2 argues against, and only the raw one needs `pragma(Pragma.AllowUnsafe)`. Multicast joins and `set_ttl` are missing but additive, so they do not block freezing; the raw/safe split does. — stable modules only change additively; this one may still change.

Async UDP networking — a typed UdpSocket over std/sys/udp for connectionless datagrams.

Every call is an io.async future on the single event-loop thread, and kernel failures throw IoExn carrying a NetError (D1) instead of resolving to a negative count. Datagram semantics are the kernel's and not softened here: a receive returns ONE datagram (never a partial one and never two), a datagram longer than the buffer is TRUNCATED with the excess discarded, and nothing is retried or ordered.

send_to addresses each datagram; send uses the peer set by connect, which for a datagram socket only fixes the default destination and the addresses the kernel will accept from — it does not establish anything.

{ UdpSocket } :: import("std/net/udp");
{ SocketAddr } :: import("std/net/addr");
{ IoExn } :: import("std/error");

e := IoExn(io : io, exn : exn);
sock := io.await(UdpSocket.bind(SocketAddr.loopback(u16(0)), io), e);
got := io.await(sock.recv_from_bytes(usize(1500), io), e);
io.await(sock.close(io), e);

Stability

unstable — the receive surface was rebuilt on 2026-09-09 (recv_from now resolves to (usize, SocketAddr) instead of taking two C out-params, and recv_bytes / recv_from_bytes gave safe code a way to read a datagram at all), so it is inside its one-release window. The open question is whether the raw pointer pair (recv, recv_from) stays exported beside the pointer-free pair: two spellings per operation is what D2 argues against, and only the raw one needs pragma(Pragma.AllowUnsafe). Multicast joins and set_ttl are missing but additive, so they do not block freezing; the raw/safe split does.

Types

UdpSocket object
UdpSocket

A UDP socket for sending and receiving datagrams.

Fields

NameTypeDescription
_fdi32
_local_addrSocketAddr
_is_closedbool

Trait Implementations

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

Create and bind a UDP socket to the given address.

Parameters

NameTypeNotes
addrSocketAddr
ioIo

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

connect : (UdpSocket) fn(self : UdpSocket, addr : SocketAddr, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)

Set the default destination for send and filter recv to datagrams from addrconnect(2) on a UDP socket. Rust: UdpSocket::connect. send/recv documented "requires prior connect" for as long as they existed while no connect did, so they could only fail with ENOTCONN.

Parameters

NameTypeNotes
selfUdpSocket
addrSocketAddr
ioIo

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

send_to : (UdpSocket) fn(self : UdpSocket, data : ArrayList(u8), addr : SocketAddr, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)

Send a datagram to a specific address. Returns the number of bytes sent.

Parameters

NameTypeNotes
selfUdpSocket
dataArrayList(u8)
addrSocketAddr
ioIo

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

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

Receive a datagram into the provided buffer. Returns the number of bytes received.

Parameters

NameTypeNotes
selfUdpSocket
buf*(u8)
sizeusize
ioIo

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

recv_from : (UdpSocket) fn(self : UdpSocket, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](Tuple(0 : usize, 1 : SocketAddr)) IoExn : IoExn)

Receive a datagram into buf, resolving to the byte count AND the sender's address — Rust's UdpSocket::recv_from.

The sockaddr buffer is allocated and decoded here. It used to be two RAW OUT-PARAMS (src_addr : *u8, src_addr_len : *u32) that every caller had to malloc, pre-set to 128, pass in, decode and free — a C signature in a Yo API, and the address was handed back undecoded.

Parameters

NameTypeNotes
selfUdpSocket
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](Tuple(0 : usize, 1 : SocketAddr)) IoExn : IoExn)

recv_bytes : (UdpSocket) fn(self : UdpSocket, max : usize, io : Io) -> Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)

Receive a datagram as bytes, resolving to at most max of them.

The pointer-free counterpart of recv. send_to/send already take an ArrayList(u8), but every receive took a *(u8) — and raw pointers are not available in safe code, so a program without pragma(Pragma.AllowUnsafe) could SEND a datagram and not receive one.

A datagram longer than max is TRUNCATED, and the excess is discarded by the kernel — that is recvfrom's behaviour, not a choice made here, so size max for the largest message the protocol allows.

Parameters

NameTypeNotes
selfUdpSocket
maxusize
ioIo

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

recv_from_bytes : (UdpSocket) fn(self : UdpSocket, max : usize, io : Io) -> Impl : (Future[Future](Tuple(0 : ArrayList(u8), 1 : SocketAddr)) IoExn : IoExn)

Receive a datagram as bytes together with the sender's address — the pointer-free counterpart of recv_from. Truncates at max, as recv_bytes does.

Parameters

NameTypeNotes
selfUdpSocket
maxusize
ioIo

Returns: Impl : (Future[Future](Tuple(0 : ArrayList(u8), 1 : SocketAddr)) IoExn : IoExn)

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

Send data to the peer set by connect. Returns the number of bytes sent.

Parameters

NameTypeNotes
selfUdpSocket
dataArrayList(u8)
ioIo

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

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

Close the socket.

Parameters

NameTypeNotes
selfUdpSocket
ioIo

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

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

Enable or disable SO_BROADCAST on the socket.

Parameters

NameTypeNotes
selfUdpSocket
enabledbool
ioIo

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

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

Get the local address this socket is bound to.

Parameters

NameTypeNotes
selfUdpSocket

Returns: SocketAddr

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

Get the underlying file descriptor.

Parameters

NameTypeNotes
selfUdpSocket

Returns: i32

impl(UdpSocket, Dispose(...))
dispose : (UdpSocket) fn(self : UdpSocket) -> 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
selfUdpSocket

Returns: unit