Standard library / std::net

std::net

A small endian/byte-order helper subset plus hosted IPv4/IPv6 TCP and IPv4/IPv6 UDP socket APIs are implemented in std/net.slk.

Async integration :

  • std::net exposes async TCP connect and accept via:
  • std::net::TCPStream.{connect_async,connect_v6_async}
  • std::net::TCPListener.accept_async
  • On supported hosted POSIX targets (linux/* and Apple Silicon macos/aarch64 today), these are backed by the hosted async runtime (async runtime) with a poll(2) fallback backend and optional Linux io_uring acceleration.
  • On other targets, these async fn wrappers complete immediately by performing the blocking socket operation.
  • Cancellation of in-flight socket operations is still follow-up work.

std::net provides networking primitives on POSIX systems.

Apple provider note: when the active security provider is auto or platform and the target is an Apple platform, silk build links Network.framework for std::net users. The current std::net implementation still exposes the hosted socket API below; Network-backed TCP/UDP and TLS transport mappings are tracked as follow-up stdlib work.

Hostname resolution (DNS) integration :

  • std::net provides resolve_host(...) and TCPStream.connect_host(...) helpers built on a small hosted POSIX getaddrinfo(3) shim.
  • This is intended for common client-side use cases (HTTP/HTTPS/SSH) where code wants to connect to host:port without binding libc directly.
  • The implementation supports:
  • IPv4 (A) and IPv6 (AAAA) results, and
  • caller-controlled selection/order via ResolveIpMode.

See also:

Exported API#

A small, non-socket subset exists in std/net.slk for early bring-up:

module std::net;

export fn bswap_u16 (x: u16) -> u16;
export fn bswap_u32 (x: u32) -> u32;

export fn htons (x: u16) -> u16;
export fn ntohs (x: u16) -> u16;
export fn htonl (x: u32) -> u32;
export fn ntohl (x: u32) -> u32;

struct Ipv4Addr {
  value: int,
}

impl Ipv4Addr {
  public fn from_octets (a: int, b: int, c: int, d: int) -> Ipv4Addr;
  public fn from_u32 (value: int) -> Ipv4Addr;
  public fn value (self: &Ipv4Addr) -> int;
  public fn a (self: &Ipv4Addr) -> int;
  public fn b (self: &Ipv4Addr) -> int;
  public fn c (self: &Ipv4Addr) -> int;
  public fn d (self: &Ipv4Addr) -> int;
  public fn is_loopback (self: &Ipv4Addr) -> bool;
}

// Compatibility wrappers (free functions).
export fn ipv4 (a: int, b: int, c: int, d: int) -> Ipv4Addr;
export fn ipv4_from_u32 (value: int) -> Ipv4Addr;

export fn ipv4_value (addr: Ipv4Addr) -> int;
export fn ipv4_a (addr: Ipv4Addr) -> int;
export fn ipv4_b (addr: Ipv4Addr) -> int;
export fn ipv4_c (addr: Ipv4Addr) -> int;
export fn ipv4_d (addr: Ipv4Addr) -> int;
export fn ipv4_is_loopback (addr: Ipv4Addr) -> bool;

Notes:

  • This is currently implemented as a byte-swap for the supported little-endian hosted baseline.

Hosted TCP API#

std::net exposes a small TCP API for hosted targets via the pluggable runtime interface std::runtime::net:

module std::net;

enum NetErrorKind {
  BadFileDescriptor,
  PermissionDenied,
  WouldBlock,
  Interrupted,
  ConnectionRefused,
  TimedOut,
  AddressInUse,
  AddressNotAvailable,
  NetworkUnreachable,
  HostUnreachable,
  NotConnected,
  BrokenPipe,
  InvalidInput,
  OutOfMemory,
  NameResolutionFailed,
  Unknown,
}

error NetFailed { code: int, requested: i64 }

export type NetIntResult = std::result::Result(int, NetFailed);

struct SocketAddrV4 {
  ip_value: int,
  port: int,
}

impl SocketAddrV4 {
  public fn from_ipv4_value (ip_value: int, port: int) -> SocketAddrV4;
  public fn from_octets (a: int, b: int, c: int, d: int, port: int) -> SocketAddrV4;
  public fn loopback (port: int) -> SocketAddrV4;
  public fn ip (self: &SocketAddrV4) -> Ipv4Addr;
  public fn port (self: &SocketAddrV4) -> int;
}

struct Ipv6Addr {
  hi: u64,
  lo: u64,
}

impl Ipv6Addr {
  public fn from_u64s (hi: u64, lo: u64) -> Ipv6Addr;
  public fn from_segments (s0: u16, s1: u16, s2: u16, s3: u16, s4: u16, s5: u16, s6: u16, s7: u16) -> Ipv6Addr;
  public fn hi (self: &Ipv6Addr) -> u64;
  public fn lo (self: &Ipv6Addr) -> u64;
  public fn is_loopback (self: &Ipv6Addr) -> bool;
  public fn is_unspecified (self: &Ipv6Addr) -> bool;
}

struct SocketAddrV6 {
  ip_hi: u64,
  ip_lo: u64,
  port: int,
  scope_id: int,
}

impl SocketAddrV6 {
  public fn from_ipv6_u64s (ip_hi: u64, ip_lo: u64, port: int, scope_id: int) -> SocketAddrV6;
  public fn from_ipv6 (ip: Ipv6Addr, port: int, scope_id: int) -> SocketAddrV6;
  public fn loopback (port: int) -> SocketAddrV6;
  public fn ip (self: &SocketAddrV6) -> Ipv6Addr;
  public fn port (self: &SocketAddrV6) -> int;
  public fn scope_id (self: &SocketAddrV6) -> int;
}

struct TCPStream {
  fd: int,
}

export type TCPStreamResult = std::result::Result(TCPStream, NetFailed);

impl TCPStream {
  public fn invalid () -> TCPStream;
  public fn is_valid (self: &TCPStream) -> bool;
  public fn connect (addr: SocketAddrV4) -> TCPStreamResult;
  public fn connect_v6 (addr: SocketAddrV6) -> TCPStreamResult;
  public fn connect_timeout (addr: SocketAddrV4, timeout_ms: int) -> TCPStreamResult;
  public fn connect_v6_timeout (addr: SocketAddrV6, timeout_ms: int) -> TCPStreamResult;
  public async fn connect_async (addr: SocketAddrV4) -> TCPStreamResult;
  public async fn connect_v6_async (addr: SocketAddrV6) -> TCPStreamResult;

  // Hostname resolution + connect-by-host helpers.
  public fn connect_host (host: string, port: int) -> TCPStreamResult;
  public fn connect_host_mode (host: string, port: int, mode: ResolveIpMode) -> TCPStreamResult;
  public async fn connect_host_async (host: string, port: int) -> TCPStreamResult;
  public async fn connect_host_mode_async (host: string, port: int, mode: ResolveIpMode) -> TCPStreamResult;

  public fn close (mut self: &TCPStream) -> NetFailed?;
  public fn read (self: &TCPStream, buf: std::arrays::ByteSlice) -> NetIntResult;
  public fn write (self: &TCPStream, buf: std::arrays::ByteSlice) -> NetIntResult;
  public fn write_all (self: &TCPStream, buf: std::arrays::ByteSlice) -> NetFailed?;
  public fn write_string (self: &TCPStream, s: string) -> NetFailed?;
  public fn shutdown_read (self: &TCPStream) -> NetFailed?;
  public fn shutdown_write (self: &TCPStream) -> NetFailed?;
  public fn shutdown (self: &TCPStream) -> NetFailed?;
}

enum ResolveIpMode {
  Any,
  PreferV4,
  PreferV6,
  V4Only,
  V6Only,
}

export type ResolveAddrsResult = std::result::Result(std::vector::Vector(SocketAddr), NetFailed);

export fn resolve_host (host: string, port: int, mode: ResolveIpMode) -> ResolveAddrsResult;
export fn resolve_host_timeout (host: string, port: int, mode: ResolveIpMode, timeout_ms: int) -> ResolveAddrsResult;

struct TCPListener {
  fd: int,
}

export type TCPListenerResult = std::result::Result(TCPListener, NetFailed);

impl TCPListener {
  public fn invalid () -> TCPListener;
  public fn is_valid (self: &TCPListener) -> bool;
  public fn listen (addr: SocketAddrV4, backlog: int) -> TCPListenerResult;
  public fn listen_v6 (addr: SocketAddrV6, backlog: int) -> TCPListenerResult;
  public fn accept (self: &TCPListener) -> TCPStreamResult;
  public fn accept_async (self: &TCPListener) -> Promise(TCPStreamResult);
  public fn local_port (self: &TCPListener) -> NetIntResult;
  public fn local_port_v6 (self: &TCPListener) -> NetIntResult;
  public fn close (mut self: &TCPListener) -> NetFailed?;
}

A positive timeout_ms covers both admission to the bounded resolver-worker set and the resolver operation itself under one absolute deadline. If all worker slots are occupied, the caller waits for a slot only until that same deadline; capacity pressure is reported as TimedOut, not an immediate WouldBlock. Runtime initialization or thread-creation failures remain ordinary resource failures.

Notes:

  • This API is currently mostly blocking (only connect_async and accept_async are integrated with the hosted event loop today).
  • This module targets hosted linux/x86_64 via std::runtime::net (POSIX sockets); wasm32-wasi has no Preview 1 sockets, so the runtime stubs return error values.
  • TCPStream/TCPListener wrap raw file descriptors; avoid copying these values until the language has move-only handle types.
  • The timeout variants use a finite hosted resolver/connect wait and report NetErrorKind::TimedOut. A successfully connected stream is restored to blocking mode with matching send/receive timeouts installed.
  • TCPListener.accept_async() duplicates the listener fd and returns Promise(TCPStreamResult). That keeps stack-local listeners usable with stored promise handles in the current async lowering model.
  • After accept_async() returns, the original listener may be closed or dropped immediately. The outstanding promise owns its duplicated listener fd and closes that duplicate when the accept completes.
  • If you want to discard error details, prefer match (r) when the Result payload may implement Drop (for example TCPStream / TCPListener), since ResultType.ok_value(r) copies the Result payload in the Supported forms.
  • std::net::stream provides task-based adapters that connect TCPStream with std::stream using producer/consumer loops:
  • std::net::stream::pipe_tcpstream_to_stream / pipe_tcpstream_to_stream_abortable
  • std::net::stream::pipe_stream_to_tcpstream / pipe_stream_to_tcpstream_abortable These adapters take ownership of the TCPStream and close it before returning.

Hosted UDP API#

std::net also exposes a small UDP API for hosted targets. The API is datagram-oriented but remains blocking.

module std::net;

struct UDPSocket {
  fd: int,
  domain: int,
}

export type NetError = NetFailed;
export type UDPSocketResult = std::result::Result(UDPSocket, NetFailed);

struct UDPRecvFrom {
  n: int,
  addr: SocketAddr,
}

export type UDPRecvFromResult = std::result::Result(UDPRecvFrom, NetError);

struct SocketAddr {
  domain: int,
  ip_value: int,
  ip_hi: u64,
  ip_lo: u64,
  port: int,
  scope_id: int,
}

impl SocketAddr {
  public fn from_v4 (addr: SocketAddrV4) -> SocketAddr;
  public fn from_v6 (addr: SocketAddrV6) -> SocketAddr;
  public fn is_v4 (self: &SocketAddr) -> bool;
  public fn is_v6 (self: &SocketAddr) -> bool;
  public fn v4 (self: &SocketAddr) -> SocketAddrV4?;
  public fn v6 (self: &SocketAddr) -> SocketAddrV6?;
  public fn port (self: &SocketAddr) -> int;
}

impl UDPSocket {
  public fn invalid () -> UDPSocket;
  public fn is_valid (self: &UDPSocket) -> bool;

  public fn bind_v4 (addr: SocketAddrV4) -> UDPSocketResult;
  public fn bind_v6 (addr: SocketAddrV6) -> UDPSocketResult;

  public fn connect_v4 (addr: SocketAddrV4) -> UDPSocketResult;
  public fn connect_v6 (addr: SocketAddrV6) -> UDPSocketResult;

  public fn local_port (self: &UDPSocket) -> NetIntResult;
  public fn close (mut self: &UDPSocket) -> NetFailed?;

  // Connected I/O (uses `read(2)` / `write(2)`).
  public fn read (self: &UDPSocket, buf: std::arrays::ByteSlice) -> NetIntResult;
  public fn write (self: &UDPSocket, buf: std::arrays::ByteSlice) -> NetIntResult;

  // Unconnected datagrams.
  public fn send_to (self: &UDPSocket, addr: SocketAddr, buf: std::arrays::ByteSlice) -> NetIntResult;
  public fn recv_from (self: &UDPSocket, buf: std::arrays::ByteSlice) -> UDPRecvFromResult;
}

Notes:

  • send_to / recv_from require the socket domain to match addr.domain (AF_INET for IPv4, AF_INET6 for IPv6).

Scope#

std::net is responsible for:

  • Sockets and basic protocols.
  • Integration with concurrency primitives (async, task).

Core Types#

  • IpAddr (V4 / V6) and SocketAddr.
  • TCPStream, TCPListener, UDPSocket.

Illustrative sketch:

module std::net;

export enum NetError {
  ConnectionRefused,
  TimedOut,
  AddressInUse,
  NetworkUnreachable,
  WouldBlock,
  Unknown,
}

export fn tcp_connect (addr: SocketAddr) -> Result(TCPStream, NetError);
export fn tcp_listen (addr: SocketAddr) -> Result(TCPListener, NetError);

Blocking vs Async#

The initial hosted baseline may be blocking I/O. Once the language’s async model is implemented, std::net should provide:

  • non-blocking sockets + integration with an event loop,
  • async fn wrappers for common operations,
  • integration with task offloading for blocking adapters (design target: std::task::run_blocking(); until that exists, users can explicitly use a task fn wrapper around blocking calls).

Considerations#

  • TLS integration beyond the shipped HTTP-oriented clients.

Source repository · Edit this page · View Markdown