← Raw Messaging Reliability | Message API and ownership →
Design Rationale — Why It Was Built This Way¶
What this chapter answers — it explains the reasoning behind zlink's core design decisions from a user's perspective. Implementation detail is owned by internals.
This document explains, from a user's perspective, the reasoning behind the core design decisions zlink adopted. Implementation detail is owned by internals; this document instead focuses on "what this choice means for the user." It's written for a reader evaluating whether to adopt zlink or trying to understand its performance characteristics.
Starting Point — What Changed From libzmq¶
zlink starts from libzmq v4.3.5 and is a library that focuses on the core patterns and narrows the surface. See the overview for the detailed comparison table. The gist is:
- 17 socket types → 8 (PAIR, PUB/SUB, XPUB/XSUB, DEALER/ROUTER, STREAM).
- The I/O engine went from a self-built poll/epoll/kqueue to Boost.Asio (bundled, no external dependency).
- Encryption went from CURVE (libsodium) to TLS (OpenSSL).
- The dependency footprint narrowed to just OpenSSL.
The reason for narrowing is simple. Exposing less makes it easier to polish each pattern deeply and keep it consistent, and gives the user fewer choices, which also means less room to choose wrong.
Core Design Principles¶
Zero-Copy — Small Messages Inline, Large Messages Reference-Counted¶
A small message (29 bytes or less) is stored directly inside the message object without a separate heap allocation (VSM, Very Small Message). A message larger than that is shared without copying, through reference counting.
What this means for the user: in a workload with many small control messages (ticks, heartbeats, short commands), allocation and copy cost disappears. The move semantics where a send "consumes" the message also come from this model — copy explicitly when you need to keep it (09 Message API).
VSM is only a memory optimization — it has no effect on the wire format. The receiving side never needs to know whether the sending side used inline storage (ZMP reference).
Lock-Free — YPipe For Inter-Thread Communication¶
Message delivery between threads uses a CAS (Compare-And-Swap)-based FIFO queue (YPipe) instead of a lock.
What this means for the user: there's no lock contention on the hot
path, so multicore scaling works well. The public socket handle API is
thread-safe: several threads may send on the same socket concurrently, while
the receiving side of one socket is kept to a single consumer thread
(11 Thread Safety).
True Async — The Proactor Pattern¶
Built on Boost.Asio, I/O completion events are delivered to a handler (Proactor). This is a structure that gets notified of completion rather than polling I/O directly.
What this means for the user: I/O completions are handled inside the I/O
threads the Context owns, and user code never runs there — Core has no
application callbacks; the application waits for readiness with a poller and
then pulls results with a socket-specific whole-message receive function or
zlink_completion_recv().
Multiple sockets are grouped under one
loop with a poller (the concept is in 02 Core API; the
per-language surface is in each
binding guide).
Protocol Agnostic — Separating Transport From Protocol¶
The wire protocol (ZMP) and the transport (tcp/ipc/inproc/ws/tls) are clearly separated.
What this means for the user: the same messaging code moves from
inproc (same process) to tcp (network) by changing only the transport —
just the address scheme changes. For tls/wss, though, beyond the address
scheme, you also need to configure zlink_set_tls_server() (server
cert/key) and, if needed, zlink_set_tls_client()
(04 Transport,
05 TLS/Security).
Going Deeper¶
- The full layered architecture: internals/architecture
- Trade-offs behind design decisions: internals/design-decisions
- The wire protocol: ZMP protocol reference
- How far the delivery guarantee goes: Reliability and delivery guarantees