ZAP Protocol
SDKs

C++

ZAP C++ SDK — the schema front-end + zero-copy runtime (cpp-core) and the high-level binding (cpp).

C++ Binding

ZAP's C++ surface is split into two repositories with distinct roles:

RepositoryRole
zap-proto/cpp-coreThe canonical schema front-end (the zap compiler binary every language plugin execs) plus the underlying zero-copy C++ runtime and the KJ support library. Derived from libcapnp; ZAP adds the whitespace-significant grammar on top.
zap-proto/cppThe high-level C++ binding — the client/server convenience API that builds on the cpp-core runtime.

If you are generating code for another language, you only need the front-end from cpp-core (it emits a binary code-generator request that your language's plugin consumes). If you are writing a C++ program, you use the cpp-core runtime headers (in namespace zap) plus the cpp binding for RPC convenience.

Build the front-end and runtime

cpp-core builds with Autotools, CMake, or Bazel:

# CMake
cmake -S c++ -B build && cmake --build build

# Autotools
cd c++ && autoreconf -i && ./configure && make -j check

make check builds the compiler (the zap_tool target) and runs the schema, compiler, and runtime test suites. The zap binary is what other runtimes shell out to.

Compile a schema

The front-end parses a .zap file and emits a binary code-generator request on stdout; a language plugin consumes it. Plugins are resolved as zap-<lang> executables on PATH.

# Emit a code-generator request to stdout (what a plugin reads on stdin).
zap compile -o- point.zap

# Drive a language plugin found on PATH.
zap compile -orust point.zap

ZAP schemas are whitespace-significant — blocks are delimited by indentation, there are no braces, and ordinals are assigned automatically. The brace form (x @0 :Float32;) still parses, and desugaring is a no-op on it, so both surfaces produce byte-identical schemas.

Build and read a message

The runtime lives in namespace zap (the cpp-core headers). You build a message with a MallocMessageBuilder, initialize the root to your generated struct type, and read it back through a generated Reader — zero-copy, straight out of the segment memory.

#include <zap/message.h>
#include <zap/serialize.h>
#include "point.zap.h"   // generated from point.zap

// Write.
zap::MallocMessageBuilder message;
Point::Builder point = message.initRoot<Point>();
point.setX(1.5);
point.setY(-2.5);

kj::Array<zap::word> words = zap::messageToFlatArray(message);

// Read (zero-copy): the reader borrows the flat array; nothing is decoded.
zap::FlatArrayMessageReader reader(words.asPtr());
Point::Reader p = reader.getRoot<Point>();
// p.getX() == 1.5, p.getY() == -2.5

writeMessageToFd / StreamFdMessageReader (in zap/serialize.h) stream a message to and from a file descriptor; serialize-packed.h and serialize-async.h provide packed and async framing.

RPC

The runtime's RPC is capability-based with promise pipelining, exposed through zap/rpc-twoparty.h. TwoPartyServer hosts a bootstrap capability over a connection; TwoPartyClient connects and retrieves it.

#include <zap/rpc-twoparty.h>

// Server: host an implementation as the bootstrap capability.
zap::TwoPartyServer server(kj::heap<PointTrackerImpl>());
server.accept(std::move(connection));   // an AsyncIoStream

// Client: connect and call the bootstrap interface.
zap::TwoPartyClient client(connection);
PointTracker::Client tracker = client.bootstrap().castAs<PointTracker>();

auto request = tracker.addPointRequest();
request.setP(point);
auto promise = request.send();          // pipelines; await with KJ's event loop

PointTracker::Server (generated) is the interface you subclass on the server side; PointTracker::Client (generated) is the typed proxy on the client side. Because a request's result can be used as the target of another call before it resolves, dependent calls collapse into a single round trip.

Documentation

The schema toolchain and core runtime are documented in zap-proto/cpp-core; the high-level binding is in zap-proto/cpp. The Schema Language and Code Generation pages cover the .zap surface this front-end parses.

On this page