Module net/udp
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
A UDP socket for sending and receiving datagrams.
A move-only value (its state is a Box): one owner closes the socket, and
a copy is E0901. Lend it with &sock, or share it as Rc(UdpSocket).
close marks the socket closed inside the box, so it is never Sync.
Fields
| Name | Type | Description |
|---|---|---|
_cell | Box(_UdpSocketState) |
impl(UdpSocket, ...)
bind : (UdpSocket) fn(addr : SocketAddr, io : Io) -> Impl(Future(UdpSocket, IoExn))connect : (UdpSocket) fn(self : &UdpSocket, addr : SocketAddr, io : Io) -> Impl(Future(unit, IoExn))Set the default destination for send and filter recv to datagrams
from addr — connect(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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
addr | SocketAddr | |
io | Io |
Returns: Impl(Future(unit, IoExn))
send_to : (UdpSocket) fn(self : &UdpSocket, data : &ArrayList(u8), addr : SocketAddr, io : Io) -> Impl(Future(usize, IoExn))Send a datagram to a specific address. Returns the number of bytes sent.
Parameters
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
data | ArrayList(u8) | |
addr | SocketAddr | |
io | Io |
Returns: Impl(Future(usize, IoExn))
recv : (UdpSocket) fn(self : &UdpSocket, buf : *(u8), size : usize, io : Io) -> Impl(Future(usize, IoExn))recv_from : (UdpSocket) fn(self : &UdpSocket, buf : *(u8), size : usize, io : Io) -> Impl(Future(Tuple(0 : usize, 1 : SocketAddr), 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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
buf | *(u8) | |
size | usize | |
io | Io |
Returns: Impl(Future(Tuple(0 : usize, 1 : SocketAddr), IoExn))
recv_bytes : (UdpSocket) fn(self : &UdpSocket, max : usize, io : Io) -> Impl(Future(ArrayList(u8), 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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
max | usize | |
io | Io |
recv_from_bytes : (UdpSocket) fn(self : &UdpSocket, max : usize, io : Io) -> Impl(Future(Tuple(0 : ArrayList(u8), 1 : SocketAddr), 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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
max | usize | |
io | Io |
Returns: Impl(Future(Tuple(0 : ArrayList(u8), 1 : SocketAddr), IoExn))
send : (UdpSocket) fn(self : &UdpSocket, data : &ArrayList(u8), io : Io) -> Impl(Future(usize, IoExn))close : (UdpSocket) fn(self : &UdpSocket, io : Io) -> Impl(Future(unit, IoExn))set_broadcast : (UdpSocket) fn(self : &UdpSocket, enabled : bool, io : Io) -> Impl(Future(unit, IoExn))local_addr : (UdpSocket) fn(self : &UdpSocket) -> SocketAddrGet the local address this socket is bound to.
Parameters
| Name | Type | Notes |
|---|---|---|
self | UdpSocket |
Returns: SocketAddr
fd : (UdpSocket) fn(self : &UdpSocket) -> i32