Language Bindings
C ABI, Java, Node, and C++ integration surface for RingLoom services.
RingLoom’s native core is written in Zig, but services do not have to be. Non-Zig integrations build on the ringloom_service C ABI, which exposes the service runtime as a shared native library with stable C-compatible handles, structs, and status codes.
The broker remains a native process. Bindings start service processes, register with a local broker, create clients, send messages, poll inbound messages, and shut down cleanly.
Native service ABI
The C ABI is defined by:
include/ringloom_service.h
src/service/c_abi.zig
Build it with:
zig build service-c
Installed outputs include:
| Artifact | Purpose |
|---|---|
zig-out/lib/libringloom_service.so or .dylib |
Shared library loaded by Java, Node.js, and other FFI consumers. |
zig-out/lib/libringloom_service.a |
Static archive for C/C++ embedders. |
zig-out/include/ringloom_service.h |
Stable C ABI header. |
The ABI version macro is RINGLOOM_SERVICE_ABI_VERSION. Opaque handles keep internal Zig structures private:
ringloom_service_tringloom_client_tringloom_message_consumer_tringloom_metrics_reader_t
ABI design principles
The C surface is intentionally low-level and explicit:
- use C-compatible scalar types, pointers, enums, and
extern structlayouts; - return
ringloom_status_tfrom fallible functions; - return handles through out-parameters;
- pass strings as pointer plus byte length;
- expose copy-based sends for convenience;
- expose claim/commit/abort for zero-copy sends;
- expose polling receive APIs driven by the caller’s thread;
- keep borrowed message payload memory valid only during the poll callback;
- never let Zig panics or Zig-specific error unions cross the ABI boundary.
This lets higher-level bindings choose their own ergonomics while sharing the same runtime contract.
C ABI capabilities
| Area | Functions / types |
|---|---|
| Lifecycle | ringloom_service_start, ringloom_service_stop, ringloom_service_destroy |
| Identity | ringloom_service_id, ringloom_service_node_id |
| Control progress | ringloom_service_poll_control |
| Clients | ringloom_service_create_client, ringloom_client_destroy |
| Discovery targets | ringloom_client_list_targets |
| Lifecycle callbacks | ringloom_client_set_lifecycle_handler |
| Copy sends | ringloom_client_send, ringloom_client_send_message, request variants, send_to, send_to_leader |
| Zero-copy sends | ringloom_client_try_claim, try_claim_to, try_claim_to_leader, request variants, ringloom_buffer_claim_commit, ringloom_buffer_claim_abort |
| Receive polling | ringloom_service_create_message_consumer, ringloom_message_consumer_poll |
| Metrics | ringloom_service_create_metrics_reader, counter/gauge registration, ring stats, counter iteration |
| Diagnostics | ringloom_status_string, ringloom_last_error_message |
Hot-path methods use status returns rather than exceptions. Expected outcomes such as buffer full, no available instance, back-pressure, and peer disconnected are normal control-flow results.
Java bindings
The Java bindings live under bindings/java and use the Java Foreign Function & Memory API.
Build and test:
zig build test-java
The Gradle project can also build a distributable JAR that embeds the platform native library. Java services use these low-level types:
| Java type | Purpose |
|---|---|
RingloomService |
Starts/stops a service and polls control messages. |
ServiceConfig |
Service startup configuration. |
RingloomClient |
Sends to discovered targets and exposes target lists. |
TargetService |
Target node ID, service ID, and leader flag. |
BufferClaim |
Reusable zero-copy send claim. |
MessageConsumer |
Polls inbound messages. |
RingloomMessage |
Borrowed view of a received message. |
RingloomMetricsReader |
Reads native counters, gauges, and ring stats. |
Java services must make control-plane progress by calling RingloomService.pollControl(limit) from an application thread. Lifecycle callbacks run synchronously on the thread that polls control messages.
For hot paths, reuse BufferClaim instances and copy payload bytes only if they must outlive the receive callback.
C++ bindings
The C++ bindings live under bindings/cpp. They are a header-only C++17 RAII wrapper around the same C ABI and native library.
Build and test:
zig build cpp-bindings
zig build test-cpp
The integration test starts a real broker, starts two C++ services, sends through a zero-copy BufferClaim, and receives through a polling MessageConsumer.
The C++ API uses exceptions for setup/convenience code through helpers such as throwIfNotOk, while hot-path operations return primitive status/count values to avoid exception allocation for expected runtime outcomes.
Node.js bindings
The Node.js bindings live under bindings/node and expose libringloom_service through a Node-API addon.
Build and test:
zig build node-bindings
zig build test-node
The addon provides RingloomService, RingloomClient, status helpers, reusable claims, borrowed payload buffers, and polling consumers. As with Java and C++, borrowed receive payloads are valid only during the callback unless copied by the application.
Choosing an integration layer
| If you want… | Use… |
|---|---|
| Native Zig services | ringloom_service directly from Zig. |
| A stable C integration surface | include/ringloom_service.h. |
| C++ RAII wrappers | bindings/cpp. |
| Java FFM and JAR packaging | bindings/java. |
| Node.js services | bindings/node. |
All bindings still rely on a running local broker. Start the broker first, then start services with matching storage path, group, and broker node ID.