Spec index | Previous: .NET | Next: Java
C++ Bindings Final Structure¶
What this chapter defines — the C++ library's
Contracts/Runtimeshape and required semantic scope.
This document defines the C++ library's public contract and ownership structure.
It does not enumerate every method. The concrete
public contract lives at bindings/cpp/include/zlink/Contracts/.
Contracts/, the installed header projection, tests, samples, the perf runner,
and runtime behavior all map
core/include/zlink.h's stable core features onto idiomatic C++ types.
This README defines how the common policy in ../README.md applies to the C++ binding.
This binding follows the common bindings architecture map using C++
naming. Contracts/ owns the installed public headers, and Runtime/
owns the private implementation under src/. The folder names organize
the repository — they are not a namespace segmentation users are meant to
depend on.
| Section | Covers |
|---|---|
| Public contract source | Contracts/Runtime source locations, the language baseline, the async surface policy link |
| Repository layout | The aligned directory tree and the file-granularity policy |
| .NET contract category projection | How the .NET layout is projected onto C++ naming |
| Public contract at a glance | The area → public object → owning header table, and a concrete facade example |
| Core feature ownership rule | The procedure to follow when adding a new feature |
| Library shape | RAII, snake_case, Pimpl, mandatory-builder rules |
| Contract/runtime placement rules | The boundary between public declarations and runtime helpers |
| Build and packaging policy | The build/link/install rules for the zlink_cpp target |
| Contract folder layout | The ownership scope of each category under Contracts/ |
| Standard interface rules | recv signatures, builders, and terminal rules |
| 64-bit byte HWM and the monitoring contract | The byte_count_t representation and the monitor snapshot fields |
| Receive flow state | The receive-flow state type, setter, and monitor surface |
| Feature scope | The groups the finished public headers cover |
| Lifetime and ownership | Resource-class release/move rules and the receive-storage rules |
| Error and result policy | Failure representation and the result domain |
| Performance policy | Hot-path and link-target constraints |
| Finished structure requirements | What to confirm before declaring the structure finished |
| Actor and Spot route results | The route-result types and Actor-directed send/request |
Public contract source¶
- Public contract:
bindings/cpp/include/zlink/Contracts/. - Runtime implementation:
bindings/cpp/src/Runtime/. - Public entry point projection:
bindings/cpp/include/zlink.hpp. - Installed projection:
bindings/cpp/include/zlink.hppandbindings/cpp/include/zlink/Contracts/.... - Compiled library: the C++ binding builds and installs a C++ library target such as
zlink_cpp, separate from the core nativezlinklibrary. - Language baseline: C++20.
- Namespace: every public type lives under
zlink. Service types live underzlink::service. - Internal implementation: native bridge helpers, callback trampolines, request-progress helpers, private
detailhelpers, private implementation headers, and.cppfiles live underbindings/cpp/src/Runtime/. -
Documentation's role: this README defines the shape, boundaries, and required semantic scope.
Contracts/owns the exact member list, and the installed headers intentionally project it. -
C++ is no longer modeled as a header-only binding.
- A second
bindings/cpp/src/zlink/Contracts/tree is never created. - The public contract stays in the installed headers, and the implementation moves behind
.cppfiles and private runtime headers. The contract/runtime separation still holds. Contracts/declares the user-facing surface, andsrc/Runtime/holds the implementation support for that surface.- Java's or .NET's interface-centric layout is never copied onto C++ as-is.
-
C++ uses installed headers, RAII classes, concrete values, and opaque implementation state as its natural boundaries.
-
C++20 is the bindings library's minimum supported baseline.
- The bindings library provides move-only
async_result_t<T>for directco_await, but it owns no coroutine executor, framework handler executor, or framework dispatcher. - A framework coroutine awaits the same object directly and uses an optional promise hook only to hand off its serial turn and ambient context.
- The per-language async execution surface baseline follows the bindings async execution surface policy.
Repository layout¶
The finished C++ binding uses the following paths consistently. The file names below are the target ownership map — a category is split further only when a public concept or runtime responsibility has an independent reason to change.
bindings/cpp/
+-- CMakeLists.txt
+-- include/
| +-- zlink.hpp
| +-- zlink/
| +-- Contracts/
| | +-- Core/
| | | +-- capability.hpp
| | | +-- context.hpp
| | | +-- context_options.hpp
| | | +-- routing_id.hpp
| | | +-- utilities.hpp
| | +-- Messaging/
| | | +-- message.hpp
| | | +-- received.hpp
| | | +-- topic_message.hpp
| | | +-- subscription_event.hpp
| | | +-- operation_contracts.hpp
| | | +-- request_result.hpp
| | +-- Sockets/
| | | +-- socket_contracts.hpp
| | | +-- message_socket_contracts.hpp
| | | +-- routed_socket_contracts.hpp
| | | +-- pubsub_socket_contracts.hpp
| | | +-- stream_socket.hpp
| | | +-- socket_options.hpp
| | | +-- results.hpp
| | +-- Eventing/
| | | +-- monitor.hpp
| | | +-- poller.hpp
| | | +-- poll_event.hpp
| | | +-- timers.hpp
| | | +-- events.hpp
| | | +-- status.hpp
| | +-- Service/
| | | +-- spot_node.hpp
| | | +-- spot.hpp
| | | +-- actor.hpp
| | | +-- spot_node_models.hpp
| | | +-- actor_models.hpp
| | | +-- operation_contracts.hpp
| | +-- Errors/
| | +-- errors.hpp
| | +-- results.hpp
+-- src/
| +-- Runtime/
| +-- zlink_cpp.cpp
| +-- Core/
| | +-- capability.cpp
| | +-- context.cpp
| | +-- utilities.cpp
| | +-- operation_detail.hpp
| | +-- runtime_helpers.hpp
| | +-- types_impl.hpp
| +-- Messaging/
| | +-- message.cpp
| +-- Errors/
| | +-- error.cpp
| +-- Eventing/
| | +-- monitor.cpp
| | +-- poller.cpp
| | +-- timers.cpp
| +-- Sockets/
| | +-- base_socket.cpp
| | +-- pair.cpp
| | +-- dealer.cpp
| | +-- pubsub.cpp
| | +-- router.cpp
| | +-- stream.cpp
| | +-- detail.hpp
| +-- Options/
| | +-- socket_options.cpp
| +-- Service/
| | +-- actor.cpp
| | +-- actor_ops.cpp
| | +-- detail.hpp
| | +-- request_reply.cpp
| | +-- spot.cpp
| | +-- spot_node.cpp
| | +-- actor_detail.hpp
| | +-- spot_state.hpp
| | +-- spot_submit.hpp
| +-- Native/
| +-- socket_handle.hpp
| +-- native_message_parts.hpp
| +-- native_parts.hpp
| +-- native_options.hpp
| +-- native_send_result.hpp
+-- native/
+-- samples/
+-- tests/
+-- perf/
CMakeLists.txt defines the compiled C++ binding target (e.g.,
zlink_cpp) and links it against the core native zlink library.
Samples, tests, perf binaries, and applications link against this target
instead of compiling the private runtime source directly.
Contracts/ is the public contract surface installed under
bindings/cpp/include/zlink/. Runtime/ is the private implementation
support under bindings/cpp/src/Runtime/. The zlink namespace and
zlink.hpp are that contract projected onto C++. Contracts and
Runtime are never exposed as namespace segments.
A runtime helper header is not public contract API. Public samples, perf,
and tests include <zlink.hpp> and link against the C++ binding library —
they never include a runtime helper path. Wrapper headers such as
include/zlink/message.hpp, include/zlink/services/spot.hpp, and
include/zlink/sockets/dealer.hpp are not part of the finished layout.
The finished tree does not replace them with forwarding headers either.
Monitor, poller, and timer contracts live under the common Eventing/
category. Contracts/Monitoring/ is not part of the finished public
contract, and the finished tree keeps no Monitoring/ forwarding header.
File-level granularity follows the common policy in ../README.md. Keep
one file per independent public concept, or per tightly coupled
operation/model group. Merge a very small marker, delegate, enum, or
pass-through helper file into the nearest contract file when doing so
makes the public shape easier to read.
.NET contract category projection¶
The C++ binding uses the .NET public contract category layout as its
classification standard. This is a projection of categories and
responsibilities, not a copy of the C# shape. C++ keeps C++20 naming
conventions, headers, RAII facades, move semantics, and concrete value
types.
.NET's file list is not copied into this document as-is. .NET's single
baseline is the .NET bindings blueprint,
specifically its Contract Folder Layout and Runtime Folder Layout
sections. This C++ README defines only the C++ projection of those
categories.
Category ownership rules are strict. A public C++ type does not move to a
different category just because its runtime implementation would be
easier to place elsewhere. Runtime helper code may be further subdivided
under src/Runtime/, but the public contract owner stays in its category.
- This projection does not strictly follow C#'s interface style.
.NET's socket role interfaces identify a socket role in the public contract. C++ is not required to exposeisocket_t,istream_socket_t,ISocket,IStreamSocketby default.- Use a concrete RAII facade unless the caller genuinely needs substitutable behavior.
- When a substitutable role is needed, keep the interface narrow, and make sure the send/receive/poll/dispatch hot path has no avoidable virtual dispatch.
Public contract at a glance¶
The finished C++ binding makes the public contract visible without adding
an interface-only layer. A user starts at <zlink.hpp> and uses the
following map to find the owning contract header. The .NET baseline's
source-of-truth detail lives in the .NET bindings blueprint; this document keeps only the C++ projection.
| Area | Public objects and role | Owning contract header |
|---|---|---|
| Core | context_t, context options, routing id, version/capability helpers |
Contracts/Core/ |
| Messaging | message_t, received_t, topic_message_t, subscription_event_t, multipart helpers |
Contracts/Messaging/ |
| Sockets | pair_socket_t, dealer_socket_t, router_socket_t, pub_socket_t, sub_socket_t, xpub_socket_t, xsub_socket_t, stream_socket_t, send/recv/request/reply builders |
Contracts/Sockets/ |
| Eventing | socket_monitor_t, monitor events, poller, poll events, timer, readiness helpers |
Contracts/Eventing/ |
| Service | spot_node_t, spot_t, actor_ref_t, the actor lifecycle model, service operation builders |
Contracts/Service/ |
| Errors | Public exception and result domain types | Contracts/Errors/ |
poller_t accepts a socket monitor as a source through void add(socket_monitor_t &monitor_, poll_event_flag_t events_, std::uintptr_t slot_),
void modify(socket_monitor_t &monitor_, poll_event_flag_t events_) and bool remove(socket_monitor_t &monitor_) (common spec
"Monitor sources in Poller"). A monitor mask accepts only pollin or none; any other bit is rejected with
config_error_t(config_result_t::invalid_argument). Drain with socket_monitor_t::recv(DONTWAIT) after readiness.
The map above is a public API index. It is the C++ equivalent of a
contract-surface overview, and does not imply abstract interfaces such as
IContext, ISpot, or IActor. A public resource object stays a concrete
RAII facade unless the caller genuinely needs substitutable behavior. A
narrow interface is allowed only for a role the user naturally swaps, such
as codec, callback, handler, or a poll target.
The public contract is read in two steps.
- Start at
<zlink.hpp>to see which public contract categories the C++ binding includes. - Open the matching
Contracts/...header to look at the concrete public type and its public member list.
For example, the finished SPOT surface looks like a concrete facade rather than an interface/implementation pair.
namespace zlink::service {
class spot_t {
public:
spot_t(spot_t&&) noexcept = default;
spot_t(const spot_t&) = delete;
send_operation_t send();
reply_operation_t reply();
int recv(received_t& out, recv_flags_t flags = recv_flags_t::none);
void close();
};
} // namespace zlink::service
zlink.hpp acts as the public table of contents for these facades.
#include "zlink/Contracts/Core/capability.hpp"
#include "zlink/Contracts/Core/context.hpp"
#include "zlink/Contracts/Core/context_options.hpp"
#include "zlink/Contracts/Core/routing_id.hpp"
#include "zlink/Contracts/Messaging/message.hpp"
#include "zlink/Contracts/Messaging/received.hpp"
#include "zlink/Contracts/Messaging/topic_message.hpp"
#include "zlink/Contracts/Messaging/subscription_event.hpp"
#include "zlink/Contracts/Messaging/operation_contracts.hpp"
#include "zlink/Contracts/Sockets/message_socket_contracts.hpp"
#include "zlink/Contracts/Sockets/routed_socket_contracts.hpp"
#include "zlink/Contracts/Sockets/pubsub_socket_contracts.hpp"
#include "zlink/Contracts/Eventing/poll_event.hpp"
#include "zlink/Contracts/Eventing/poller.hpp"
#include "zlink/Contracts/Service/spot_node.hpp"
#include "zlink/Contracts/Service/spot.hpp"
#include "zlink/Contracts/Service/actor.hpp"
#include "zlink/Contracts/Errors/errors.hpp"
Runtime detail lives behind the facade. A public header may name an opaque implementation state, but never exposes a native handle, a callback trampoline, whole-message array handling, the request pump, or a marshalling helper.
namespace zlink::service {
class spot_t {
public:
spot_t(spot_t&&) noexcept;
spot_t(const spot_t&) = delete;
~spot_t();
send_operation_t send();
reply_operation_t reply();
void close();
private:
struct impl;
std::unique_ptr<impl> impl_;
};
} // namespace zlink::service
This structure keeps the public surface easy to scan while preserving C++
ownership semantics. spot_t, spot_node_t, and actor_ref_t are the
contract; src/Runtime/... and the private zlink::detail helpers are
implementation support.
Core feature ownership rule¶
Every stable core feature C++ exposes follows this ownership rule.
- Add the public type or method to the correct
bindings/cpp/include/zlink/Contracts/category. - Update
bindings/cpp/include/zlink.hppand any intentionally installed projection header. - Decide the C++ domain owner: one of context, message, socket, monitor, timer, service, SPOT, actor, error, option.
- Put raw C handle access, whole-message array handling, callback userdata, trampoline state, and native marshalling helpers in
src/Runtime/headers and.cppfiles. - Add a public header test and update at least one sample/perf case when the new feature affects a user workflow or a measurement.
- Confirm the new public API isn't just a thin C wrapper. Keep it internal if it merely delegates without improving ownership, validation, or shape.
- Never leave behind a public name that's fallen out of use. Do not keep an aliasing deprecated form, a forwarding overload, or an alternate public header, unless a later document explicitly changes this C++ policy.
For explicitly acquiring a Spot by routing id, the C++ binding exposes
spot_node_t::get_or_create_spot(routing_id_t), which maps directly to
zlink_spot_node_spot_get_or_new(...). This method returns an owned
spot_t facade and a creation flag. This behavior is never implemented by
combining spot_lookup() and create_spot().
Library shape¶
The C++ binding should feel like a small native C++ library layered on the core C contract.
- A public resource object is an RAII class that owns or borrows a native handle, per its documented lifetime.
- The destructor releases the resource without requiring the caller to know the native close order. The resource destructor, and any other non-trivial method, is defined out-of-line in a
.cppfile. - A small value-type operation may stay inline when it does not expose native ownership, callback state, request state, or marshalling detail.
- Public methods use
snake_case. - Public value types — message, routing id, received metadata, topic message, result, error, enum, option — stay concrete.
- A public resource header uses an opaque implementation state, such as Pimpl, to keep native handle layout, callback state, request state, and ABI-sensitive storage from leaking into the contract.
- Templates, overloads, and move semantics are used only to simplify caller ownership or avoid a copy — never to expose template machinery as a substitute for a clear domain type.
- A virtual interface is used only when the caller needs substitutable behavior. Not every handle is wrapped in an abstract interface by default.
- A builder is required for multipart send, publish, request, reply, actor, and SPOT operations — this hides native request state and makes ownership clear.
Contract/runtime placement rules¶
- Public declarations and user-visible behavior live in
Contracts/. - A public free function, static helper, extension-style helper, or builder convenience helper lives in
Contracts/when the caller can call it directly. - Runtime handle owners, the socket kernel, the request pump, callback trampolines, and whole-message array helpers live in
src/Runtime/. - FFI declarations, raw C handles, native struct mirrors, marshalling helpers, and platform loading code live in
src/Runtime/Native/. zlink.hppprojectsContracts/. ARuntime/helper path is never given a public-include style.- A contract header never includes a private runtime header. If a public class needs implementation state, it exposes only an incomplete
impltype or another opaque private member, and defines behavior in the.cppfile. - A runtime concrete class is not a user entry point. If the behavior is public,
Contracts/declares it andRuntime/implements it.
Build and packaging policy¶
Once C++ moves off header-only, the binding carries one more compiled artifact. The finished binding therefore keeps the following build rules.
- The C++ binding builds a
zlink_cpplibrary target. zlink_cpplinks against the core nativezlinklibrary and follows a version-compatibility rule with the core library.- The Linux, macOS, and Windows packages build the C++ library for each supported architecture and runtime toolchain.
- CMake install/export metadata lets an application consume the public headers together with the compiled C++ binding target.
- Samples, tests, and the perf runner link against the same installed-style C++ target an application uses — they never depend on a private runtime source path.
- The runtime search path, DLL lookup rules, and packaged native artifacts are tested, since an application now loads the core native library and the C++ binding library together.
- Public headers avoid exposing ABI-sensitive implementation storage. Public method signatures may keep C++ idiom, but native handle layout, callback state, request state, and marshalling buffers stay outside the installed headers.
Contract folder layout¶
Contracts/ is the source ownership map for public C++ declarations.
zlink.hpp projects these categories into the zlink namespace.
Core/: context, context options, routing id, utility resources, and public free functions such as version or capability helpers.Messaging/: message, received metadata, topic message, subscription event, stream packet values, and builder payload helpers. Codec distribution scope follows the common raw payload policy.Sockets/: socket operations, socket family, typed options, request/reply, publish/subscribe surfaces.Eventing/: monitor, monitor snapshot/event, poller, poll event, timer, public poll helpers.Service/: SPOT node, SPOT handle, the topology model, actor ref, actor lifecycle, operation builders.Errors/: an exception or typed error-result domain.- Enum, flag, and result types live in the category that defines their meaning. No
Enums/folder is created just to group declarations by syntax.
Standard interface rules¶
- Data-plane
recv, routed recv, subscribe, and subscription-event receive use caller-provided output storage (e.g.,received_t&,topic_message_t&,subscription_event_t&). message_t::from(...)copies the caller's bytes independently. When a caller-owned buffer must be handed to a message without a copy, use the advanced API overloadexternal_message_t::from(span, free_fn, hint). This overload entrusts the buffer to the message, and callsfree_fn(data, hint)exactly once when the message releases the buffer.- send, routed send, publish, request, reply, SPOT operations, and Actor location/session operations return a move-only fluent builder.
- A builder's start method takes only a target identity, topic, channel, routing ID, or
reply_token_t. Payload, flag, timeout, and async submit choices happen at the builder stage. - A SPOT channel-targeted operation uses
send_to_channel(...)andrequest_to_channel(...). SPOT topic publish keepspublish(topic)as-is. - No single-payload shortcut overload is added under the same name as an operation's start method.
send(message),send(routing_id, message),publish(topic, message),send_to_channel(channel, message),send_to_spot(..., message)are not public contract members. A caller usessend(...).message(message).submit(). - A multipart payload accumulates via repeated
message(...)calls. Amessages(...)convenience method is allowed only when it delegates to the same builder contract and is declared inContracts/. - A Dealer socket does not expose protocol envelope helpers such as
request_frame(...)orreply(request_token, parts). Dealer can start a request withrequest(), but has no API-level peer routing id, so it cannot reply to an arbitrary token. - No operation-start overload family such as
send_no_wait,publish_with_flags,request_asyncis added. Keep one operation name, and let the builder absorb variants. The per-language name of the terminal builder method follows the bindings async execution surface policy. - Send's blocking
submit()uses CoreNONEadmission, whileasync()waits for a CoreDONTWAITcompletion. SocketSNDTIMEObounds the blocking admission wait. The binding does not create a payload retransmission queue. - The C++ binding adds no lock or gate of its own to an outbound path. It materializes every builder part in one native array and calls the Core whole-message API once. Core atomically submits the array as one record and consumes every slot, while the binding's separate native view preserves the public C++ message. Core handles independent concurrent submits; the binding does not serialize, wait, or retry. Core's lifecycle gate likewise owns races between close and an in-flight submit.
- Request provides blocking
submit()andasync()and retains the builder's reply timeout. It captures the target when the operation is created and does not use physical connection identity as a public target. - The request timeout is Core-owned (
ZLINK_REQUEST_TIMED_OUT); the builder'stimeout(...)sets that Core-owned reply deadline. A submit failure is thrown assubmit_error_t; after admission, Core's reply lifecycle owns completion. Dropping the awaitable detaches only the waiter; runtime drain cleans up the late completion. publish_operation_t(PUB/XPUB) has only synchronoussubmit() -> bool. PUB/XPUB publish is lossy, so the publish builder has noasync()terminal;ZLINK_PUB_OPT_NODROPbackpressure is surfaced only by synchronous submit.- The terminal for a raw ROUTER/
received_treply is the synchronous one-shotreply_submit_operation_t::submit() -> void. It submits a terminal reply or error reply with one native call. A DEALER peer is subject to Application HWM (the queue byte threshold),PAUSED, andSNDTIMEO, so the result can beBACKPRESSURED; a ROUTER peer uses the HWM-free Completion connection.NOT_CONNECTED,TERMINATED,INVALID_ARGUMENT, and other submit failures are delivered immediately assubmit_error_t.
64-bit byte HWM and the monitoring contract¶
- Socket HWM and the context Core HWM memory limit and budget are expressed as
byte_count_t. - This value type holds only
uint64_tbytes, and reveals its unit through thebytes(...)constructor function and thebytes()accessor function. - The public contract has no
message_count_talias or adapter. 0means unlimited for HWM, and the manual default is4,096,000 bytes.
auto options = socket.options ();
options.send_hwm (zlink::byte_count_t::bytes (send_limit)); // Sets the send pipe's byte HWM.
options.recv_hwm (zlink::byte_count_t::bytes (0)); // 0 means unlimited receive HWM.
auto context_options = context.options ();
context_options.core_hwm_memory_limit_bytes (
zlink::byte_count_t::bytes (memory_limit));
context_options.core_hwm_budget_bytes (
zlink::byte_count_t::bytes (core_budget));
context_options.core_hwm_profile (zlink::auto_hwm_profile::balanced);
const auto snapshot = context.core_hwm_budget_snapshot ();
context.reset_core_hwm_budget_metrics ();
For core_hwm_memory_limit_bytes(...) and core_hwm_budget_bytes(...), 0
means that the explicit input or manual Core budget is absent. The binding does
not calculate profile ratios, connection counts, or per-queue HWM values; it
passes the exact uint64_t values to Core context options.
The C++ binding supplies no runtime memory hint. Input precedence is manual Core
budget, explicit memory limit, then Core fallback. If an explicit input exceeds
a finite hard limit Core detected, the binding preserves EINVAL and does not
clamp it.
The core_hwm_budget_snapshot_t value projects Core ABI v1 fields and flags without
unit conversion, and core_hwm_budget_snapshot() owns ABI-version and structure-size
initialization. A direction on which the caller uses send_hwm(...) or
recv_hwm(...) remains a manual override.
The snapshot includes configured/runtime/resolved memory limits,
configured/effective budgets, planned/applied/manual-reserved HWM, Core-queue/
application/current/peak/provisional accounted bytes, completion current/peak/
pending and total-messaging values, monitor/instance aggregates, application/
completion queue counts, outstanding_application_lease_count,
retired_queue_count, deferred_origin_credit_bytes, oversize/blocked/aggregate
flags, budget_generation, and measurement_epoch. Metrics reset preserves
current, pending, and queue-count gauges, rebases budgeted and completion peaks
to their current values, clears epoch counters, and increments
measurement_epoch. application_accounted_bytes and the three
owner-lifecycle fields are ABI-reserved and always zero.
Planning, manual overrides, and admission follow the Core HWM contract.
0 bytes means unlimited, not an allowance for one message.
socket.monitor_open(events, monitor_hwm_bytes) and
socket_monitor_t::open(socket, events, monitor_hwm_bytes) take a
byte_count_t. Zero selects the Core monitor default; a positive value is
forwarded unchanged as the exact monitor queue byte HWM. There is no
message-count overload or alias.
The outputs of recv(received_t&), subscribe(topic_message_t&), recv(message_t&), and
subscribe_part(...) manage part and routing/topic/request metadata lifetime through C++ copy,
close(), and destruction. The common receive ownership contract defines the boundary with receive accounting.
The public contract has no auto_hwm_msg_unit_bytes or slot, size-cap, or connection-bucket planner
properties or aliases. The monitor snapshot projects
Core monitoring ABI v4 byte-pending fields, keeping pending-message counts
separate from snd_pending_bytes and rcv_pending_bytes; context-wide budget, accounting,
and queue counts come from core_hwm_budget_snapshot_t.
Receive flow state¶
zlink::receive_flow_state_t is an enum class over int with running = 0 and paused = 1.
The setter is void socket_t::set_receive_flow_state(receive_flow_state_t); a failed native
result is thrown as config_error_t carrying that result.
State, result, and monitor projection follow the common receive-flow contract.
Feature scope¶
The finished C++ binding's public headers cover the following groups.
- Core: context, version/capability helpers, context options, shutdown, auto-HWM recalculation,
atomic_counter_t,stopwatch_t,thread_t. - Messaging: message ownership, builder multipart input, received metadata, topic message, subscription event, routing id, callback types.
- Socket family: pair, dealer, router, pub, sub, xpub, xsub, stream, stream-bound actor snapshot, common options, typed socket options, bind/connect/disconnect, TLS, callbacks, request/reply surfaces.
- Eventing: socket monitor, monitor event, monitor snapshot, poller, one-shot
poll(...), poll event, timer, readiness flags. - Services: SPOT node, SPOT handle, topology snapshot, actor ref, actor lifecycle, actor operations.
- Errors: a typed exception or error-result surface that preserves the core result domain.
The C++ surface never exposes a raw native handle, whole-message array handling, callback userdata, an internal inproc endpoint, or a request pump object as a public concept.
Lifetime and ownership¶
A C++ caller never has to reason about cleaning up a C handle.
- A resource class releases its native handle in the destructor, and supports an explicit
closeor an equivalent lifecycle method when close can fail. - A move-only resource class is preferred over shared ownership of a mutable handle.
- A message value supports an efficient move; explicit payload share/transfer/duplicate
follow the common contract's
Copy/Move/Clone(Czlink_msg_copy/zlink_msg_moveand a deep copy). The C++ signatures aremessage_t::copy()(share, returns a new value),message_t::move(message_t&)(ownership transfer, caller left empty), andmessage_t::clone()(independent deep copy);movewraps the C API directly, separate from C++ move semantics. See the common Message ownership contract §"명시적 Copy / Move / Clone". - The data-plane receive and subscribe paths use caller-provided storage.
- Receive-result lifetime APIs follow the C++ output-object description in 64-bit byte HWM and the monitoring contract.
- A service control/admission receive path, such as Actor join request receive, may use an optional or a typed result return when that's clearer for a C++ caller — but it must still distinguish no-data from a hard receive failure.
- A callback keeps the native callback lifetime and the user callable's lifetime internally consistent.
Error and result policy¶
A binding may use either exceptions or typed result objects, but the public shape preserves the core meaning.
- No-data and transient backpressure are kept distinct from a hard failure.
- request, submit, recv, bind, connect, config, handler, and close failures preserve the result domain meaning.
polloutis a send-recovery readiness signal, not an ordinary writable bit.- The ROUTER/PUB defaults, SPOT HWM defaults, and SPOT dispatch worker semantics follow the core header.
Performance policy¶
- A multipart value is created directly from the array filled by Core whole-message receive.
- The hot path avoids unnecessary heap allocation, avoidable copies, reflection-like dynamic dispatch, hidden waits, sleeps, busy waits, broad locks, and joins.
- Perf and samples include only the installed public headers.
- Perf and samples link against the public C++ binding target — never against a private runtime object file or a helper source directory.
- C++ perf semantics match
bindings/c/perf: the same pattern semantics, the same transport semantics, the same client-count policy, with no private fast path.
Finished structure requirements¶
The finished C++ binding satisfies the following requirements.
- The installed headers expose every stable, user-facing core feature.
- The C++ binding builds and installs a compiled C++ library target in addition to the public headers.
Contracts/Eventing/is the only public eventing category.Contracts/Monitoring/is gone, andzlink.hppincludes the Eventing header.- Applications, samples, perf, and tests include only
<zlink.hpp>or an intentionalContracts/...header. - The public headers and the compiled C++ binding target alone give an application, perf, sample, or framework adapter everything it needs.
- A user never needs a private helper header or a private runtime source path.
- A value type stays concrete unless an abstraction genuinely reduces complexity.
- The public API hides native whole-message array handling, raw handles, and callback userdata.
- Handler registration uses the
set_..._handlername, and no publicon_...alias is kept. - A public helper/free function and a builder convenience method are declared in
Contracts/, not as a runtime helper. - An exception where service control/admission receive differs from the data plane's caller-provided storage is documented.
- Perf tests use the same measurement semantics as C perf.
Actor and Spot route results¶
C++ exposes Actor and Spot route lookup results as concrete contract types.
actor_route_tpreserves the resolved Actor ref,actor.node_rid,current_spot_rid,current_spot_kind.spot_route_tpreservesspot_rid,owner_node_rid,spot_kind.spot_kinddistinguishes an Entry Spot from a user Spot. An invalid kind is not a successful route result.-
spot_node_spot_entry_tandspot_node_actor_entry_texpose the same Spot kind/current Spot fields as the core snapshot. -
C++ exposes
spot_node_t::send_to_actor(actor_ref_t)andspot_node_t::request_to_actor(actor_ref_t), which take a resolved Actor ref. send_to_actor, once submit succeeds, transfers ownership of one or more message parts, and completes once the Actor owner's mailbox takes them over.request_to_actor, once submit succeeds, transfers ownership of the request part and delivers the reply part the Actor handler produced as a native-awaitable result.- C++ must not resurrect the removed Discovery route table or resolver API as a compatibility helper.
Pull completion public contract¶
C++ package information follows its distribution metadata; the Core ABI version follows Core release metadata.
C++ provides blocking submit() and async() returning a result object (send_submission_t/request_submission_t: result and admitted, plus reply for a request).
Completion-wait lifetime ends through async_result_t drop.
Native completion IDs, user_context, and raw drain are not public APIs.
Submission results follow the common result projection;
completion joins, lifetime, and progress conditions for poll_event_flag_t::pollcompletion follow the
async execution model.
Only a ROUTER REQUEST receive creates a reply_token_t. The token carries a shared owner tag created by
the ROUTER wrapper and an opaque value. Equality, hashing, and reply-owner validation use both values.
It provides no public numeric constructor, raw accessor, ordering, serialization, or close operation.
stream_packet_t is a move-only reusable output that first clears its previous payload on recv entry.
Concurrent recv into the same output is invalid-state. Header/body references remain valid only until
the next recv entry or close(). Before the first bind/connect, the receive-mode setter accepts only
raw and packet and rejects unspecified.
Public interface¶
struct send_submission_t {
zlink_submit_result_t result; // OK | BACKPRESSURED, submit-time snapshot
async_result_t<void> admitted; // completed when result is OK
};
struct request_submission_t {
zlink_submit_result_t result;
async_result_t<void> admitted;
async_result_t<std::vector<message_t>> reply; // completes after successful admission
};
class send_submit_operation_t {
public:
send_submit_operation_t&& message(message_t&) &&;
send_submit_operation_t&& message(message_t&&) &&;
void submit() &&;
send_submission_t async() &&;
};
class request_submit_operation_t {
public:
request_submit_operation_t&& message(message_t&) &&;
request_submit_operation_t&& message(message_t&&) &&;
request_submit_operation_t&& timeout(std::chrono::milliseconds) &&;
std::vector<message_t> submit() &&;
request_submission_t async() &&;
};
class reply_token_t final {
public:
reply_token_t() = delete;
reply_token_t(const reply_token_t&) = default;
reply_token_t& operator=(const reply_token_t&) = default;
friend bool operator==(const reply_token_t&, const reply_token_t&) noexcept;
private:
reply_token_t(std::shared_ptr<const void> owner, uint64_t value) noexcept;
std::shared_ptr<const void> owner_;
uint64_t value_;
friend struct detail::received_access_t;
friend struct reply_token_hash_t;
};
struct reply_token_hash_t {
std::size_t operator()(const reply_token_t&) const noexcept;
};
class reply_submit_operation_t {
public:
reply_submit_operation_t&& message(message_t&) &&;
void submit() &&;
};
enum class stream_recv_mode_t : int {
unspecified = 0,
raw = 1,
packet = 2
};
class stream_packet_t final {
public:
stream_packet_t() = default;
~stream_packet_t();
stream_packet_t(stream_packet_t&&) noexcept = default;
stream_packet_t& operator=(stream_packet_t&&) noexcept = default;
stream_packet_t(const stream_packet_t&) = delete;
stream_packet_t& operator=(const stream_packet_t&) = delete;
bool empty() const noexcept;
const std::optional<routing_id_t>& routing_id() const noexcept;
message_t& header();
message_t& body();
void close() noexcept;
private:
std::optional<routing_id_t> routing_id_;
std::optional<message_t> header_;
std::optional<message_t> body_;
friend class stream_socket_t;
};
bool stream_socket_t::recv_packet(
stream_packet_t& out, recv_flags_t flags = recv_flags_t::none);
stream_recv_mode_t stream_socket_options_t::recv_mode() const;
void stream_socket_options_t::recv_mode(stream_recv_mode_t mode);
The operation-start signatures are PAIR send_operation_t send(), DEALER
send_operation_t send() and request_operation_t request(), ROUTER
send_operation_t send(const routing_id_t&),
request_operation_t request(const routing_id_t&), and
reply_operation_t reply(const routing_id_t&, reply_token_t), and STREAM
send_operation_t send(const routing_id_t&). A send factory captures the target in the builder.
received_t::reply_token() returns const std::optional<reply_token_t>&.
received_t::send() returns a send_operation_t that captures the source target, and
received_t::reply() returns a reply_operation_t that captures the source RID and token.
The public C++ surface contains no send/request/reply .flags(...), send .timeout(...), request
callback overload/type, async_result_t::cancel(), STREAM packet handler, monitor on_event or
ignore_event, timer on_fire, pair/generation member, or exact-pair method. PAIR and STREAM send
submit() also do not return bool. routed_send_operation_t and
routed_send_submit_operation_t are not public types.
Monitor provides optional<monitor_event_t> recv(recv_flags_t = none), status(), and close().
Timer provides start(duration, uint64_t = 0), stop(), optional<uint64_t> recv(), and close().
The pending native option names are ZLINK_OPT_PENDING_MAX_MSGS and
ZLINK_OPT_PENDING_MAX_BYTES. Monitor-event connection_id is used only for diagnostics and
correlation, not as a send/reply target or reconnect fence. Pending native options do not add a public
high-level option facade.
Implementation and contract-test verification requirements¶
Verify the following using only the public C++ interface, return values, exceptions, and poller events. Each item maps to one contract test.
Operations and completion
- Every socket's send factory returns
send_operation_t, and blockingsubmit()andasync()observe theNONEandDONTWAITcompletion boundaries respectively. - Request exposes only timeout, a blocking result, and
async(). - Common completion, cancellation, and poller observations follow the execution-model verification requirements.
- When HWM/
PAUSEDwaiting expires for a raw reply submitted to a DEALER peer,submit_error_treportsBACKPRESSURED; a reply submitted to a ROUTER peer retains the HWM-free result of the Completion connection.
ReplyToken and STREAM
- Tokens with the same ROUTER owner and opaque value are equal; tokens with different owners are not. Reply with a token from another owner fails before the native call.
- Moving a ROUTER wrapper preserves owner-tag identity. After close and recreation, a stale token cannot be used with the new wrapper.
recv_packet()fills the output on success and leaves it empty onNO_DATAor error. The same output can be reused afterclose().
Pull eventing
- Monitor DONTWAIT no-data is
nullopt, timer no-data is alsonullopt, and the pull lifecycle exposes each event and fire count once.