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.
Fields
| Name | Type | Description |
|---|---|---|
_fd | i32 | |
_local_addr | SocketAddr | |
_is_closed | bool |
Trait Implementations
impl(UdpSocket, ...)
bind : (UdpSocket) fn(addr : SocketAddr, io : Io) -> 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 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 |
send_to : (UdpSocket) fn(self : UdpSocket, data : ArrayList(u8), addr : SocketAddr, io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)recv : (UdpSocket) fn(self : UdpSocket, buf : *(u8), size : usize, io : Io) -> 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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
buf | *(u8) | |
size | usize | |
io | Io |
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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket | |
max | usize | |
io | Io |
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)send : (UdpSocket) fn(self : UdpSocket, data : ArrayList(u8), io : Io) -> Impl : (Future[Future](usize) IoExn : IoExn)close : (UdpSocket) fn(self : UdpSocket, io : Io) -> Impl : (Future[Future](unit) IoExn : IoExn)set_broadcast : (UdpSocket) fn(self : UdpSocket, enabled : bool, io : Io) -> Impl : (Future[Future](unit) IoExn : 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) -> i32impl(UdpSocket, Dispose(...))
dispose : (UdpSocket) fn(self : UdpSocket) -> unitRelease 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
| Name | Type | Notes |
|---|---|---|
self | UdpSocket |
Returns: unit