A pure Zig implementation of Cap'n Proto -- a serialization framework and RPC system. Includes a compiler plugin (capnpc-zig), a message serialization library, and an RPC runtime built on std.Io with a concurrent read/write transport. Targets Zig 0.17-dev.
Status (v0.18.0): serialization, codegen, the
capnpc-zigplugin, and the two-party RPC core are Stable on a frozen, CI-gated public surface (docs/api-snapshot.txt). The L3/L4 three-party arc, the reflected-cap resolver, QUIC, persistence vat-restore, events, binary schema reflection, and the demoted transport/ctor variants remain Experimental and may change at any 0.x minor bump. Pre-1.0 — pin an exact version. Seedocs/supported-surface.mdfor the full contract anddocs/stability.mdfor the per-module matrix.
- Pure Zig Implementation: No C++ dependencies, targets Zig 0.17-dev
- Full Serialization Support: Complete Cap'n Proto wire format including packed encoding and far pointers
- Zero-Copy Deserialization: Readers work directly with message bytes
- Builder Pattern: Ergonomic API for constructing messages
- Schema-Driven Code Generation: Generates idiomatic Zig Reader/Builder types from
.capnpschemas - Executable Type Fidelity: Brand-aware schema validation/canonicalization and finite typed generic views without removing erased APIs
- Binary Schema Reflection (Experimental): Embedded schema nodes, type and field lookup, and dynamic readers/builders with generic bindings and schema evolution
- RPC Runtime: Cap'n Proto RPC over TCP with capability-based messaging
- Optional QUIC RPC: Baseline and native modes, including real
Peerfanout, close-isolation coverage, and an embedded (foreignquic.app.Driverhost) session seat for ALPN-routed multi-protocol listeners - Comprehensive Tests: Extensive message/codegen/RPC/interop coverage
- Type Safe: Leverages Zig's compile-time type system
Fetch a tagged release into your build.zig.zon (zig fetch --save records the
.hash):
zig fetch --save git+https://github.com/nullstyle/capnp-zig.git#v0.18.0Then import capnpc-zig (full: serialization + codegen + RPC) or
capnpc-zig-core (serialization + codegen only) — see
docs/build-integration.md for the complete
build.zig wiring, including running capnp compile during your build.
- Zig 0.17-dev on
PATH(minimum declared inbuild.zig.zon; helper tools remain inmise.toml) - Cap'n Proto compiler (
capnp) - optional, for schema compilation mise(recommended, for environment management)just(recommended, for task automation)- Docker (optional, for local GitHub Actions runs via
act)
# Using just (recommended)
just build
# Or using zig directly
zig build
# Run tests
just test
# or
zig build test --summary allOn Windows, the just test recipes serialize build-runner jobs to avoid the
pinned Zig process-inheritance defect. Full-suite recipes compile binaries in
parallel first. The equivalent direct commands are:
mise exec -- zig build test-compile --summary all
mise exec -- zig build test -j1 --summary all
# Use -Doptimize=ReleaseSafe on both commands for the full safety-enabled suite.Keep these as separate invocations so all compiler processes exit before tests start. Tests retain their own threads, RPC concurrency, and deadlines. See Windows runner evidence for the failure mechanism and the condition for removing this workaround.
The exact Zig toolchain is pinned in mise.toml — the single specifier for
both CI and local development, tracking zig master. Read that file for the
current value; it is deliberately not repeated here so it cannot go stale.
mise install gets it; CI installs from the same file and asserts the
toolchain on PATH matches it. build.zig.zon carries a floor
(minimum_zig_version), not a second pin. Zig 0.16 is no longer a supported
target for this branch; downstream consumers should use a compatible 0.17-dev
snapshot until Zig 0.17 stabilizes.
If you manage Zig with zvm, its PATH entry takes precedence over mise's shims —
use mise exec -- zig ... to match CI exactly.
Repository generation and package checks also pin the Cap'n Proto schema
compiler in tools/capnp-toolchain.json. Run
mise run bootstrap:capnp once, then use mise exec -- just check-generated
or other repository commands. The bootstrap verifies the source archive's
SHA-256 and builds the same compiler on Linux, macOS, and Windows under
.zig-cache/capnp-toolchain; mise selects its installed bin directory.
mise run check:capnp fails if PATH selects a different version. Lossless
reflection retains compiler-provided node layouts and source byte ranges, so
changing this input changes generated bytes even when field APIs are identical.
The standard schemas and the independent C++ conformance reference remain
separately pinned by the vendor/ext/capnproto submodule.
Linux, macOS, and Windows are all first-class targets and development operating systems, gated per push in CI. The per-layer platform matrix (including the few upstream-blocked features) lives in docs/stability.md.
Add capnpc-zig to your project and use the message serialization API directly:
const std = @import("std");
const capnpc = @import("capnpc-zig");
const message = capnpc.message;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create a message builder
var builder = message.MessageBuilder.init(allocator);
defer builder.deinit();
// Allocate a struct with 1 data word and 2 pointer words
var struct_builder = try builder.allocateStruct(1, 2);
// Write primitive fields
struct_builder.writeU32(0, 42);
struct_builder.writeU32(4, 100);
// Write text fields
try struct_builder.writeText(0, "Hello");
try struct_builder.writeText(1, "World");
// Serialize to bytes
const bytes = try builder.toBytes();
defer allocator.free(bytes);
// Deserialize
var msg = try message.Message.init(allocator, bytes, .{});
defer msg.deinit();
const root = try msg.getRootStruct();
// Read fields
const value1 = root.readU32(0); // 42
const value2 = root.readU32(4); // 100
const text1 = try root.readText(0); // "Hello"
const text2 = try root.readText(1); // "World"
}For a Cap'n Proto schema like:
@0x9eb32e19f86ee174;
struct Person {
name @0 :Text;
age @1 :UInt32;
email @2 :Text;
}The generated Zig code provides:
const std = @import("std");
const capnpc = @import("capnpc-zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create a Person
var msg_builder = capnpc.message.MessageBuilder.init(allocator);
defer msg_builder.deinit();
var person_builder = try Person.Builder.init(&msg_builder);
try person_builder.setName("Alice");
try person_builder.setAge(30);
try person_builder.setEmail("alice@example.com");
// Serialize
const bytes = try msg_builder.toBytes();
defer allocator.free(bytes);
// Deserialize
var msg = try capnpc.message.Message.init(allocator, bytes, .{});
defer msg.deinit();
const person_reader = try Person.Reader.init(&msg);
// Access fields
const name = try person_reader.getName();
const age = person_reader.getAge();
const email = try person_reader.getEmail();
}For a canonical build.zig codegen + generated-module wiring example, see docs/build-integration.md.
Generated Builders also support field getters, typed copy setters, clearing,
and asReader() with explicit borrowed-reader storage. Concrete generic list
and finite recursive applications have typed brands() views in full and
compact profiles. See the generated API guide for an
executable example, strict Text reads, lifetime rules, and remaining RPC limits.
Generated structs, groups, enums, and interfaces expose capnpSchema. The
module's CAPNP_SCHEMA_REQUEST contains the original binary schema nodes,
including the compiler-provided dependencies, defaults, annotations, and brands.
Load a registry once, resolve a type, and inspect or modify messages by field
name through capnpc.reflection.DynamicStruct. Full and core library modules
both export reflection.
See the reflection guide for an executable example,
ownership rules, schema evolution, and the limits of this initial API.
--no-reflection omits this metadata; --no-manifest independently controls the
legacy JSON export-name manifest. Use matching generator and runtime revisions
when reflection is enabled.
The implementation follows a four-layer design, each building on the previous:
src/serialization/message.zig + src/serialization/message/
Core Cap'n Proto binary format: segment management, pointer encoding/decoding, struct/list/text/data serialization, packed encoding, and far pointers. Key types: MessageBuilder, Message, StructBuilder, StructReader.
src/serialization/schema.zig, src/serialization/request_reader.zig, src/serialization/schema_validation.zig
Schema type definitions (Node, Field, Type, Value), CodeGeneratorRequest parsing from stdin, and schema validation/canonicalization.
src/capnpc-zig/
Generates idiomatic Zig Reader/Builder types from Cap'n Proto schemas. generator.zig is the main driver; struct_gen.zig generates field accessors; types.zig maps Cap'n Proto types to Zig types.
src/rpc/
Cap'n Proto RPC over TCP and optional QUIC using std.Io with a concurrent
read/write transport. Organized by domain:
- Wire (
src/rpc/wire/): Message framing and typed RPC wire message readers/builders. - Capabilities (
src/rpc/caps/): Capability tables, capability pointers, lifecycle helpers, and payload remapping. - Promises (
src/rpc/promises/): Promised-answer transforms, queued pipelined-call replay, return routing, and return-send helpers. - Transport (
src/rpc/transport/): Peer-facing binding contracts plus TCP and optional QUIC transport backends. - Peer (
src/rpc/peer/): Inbound/outbound call orchestration, return handling, capability lifecycle, embargo handling, third-party handoff, and forwarding logic. - Integration (
src/rpc/integration/): Host-facing adapters such asHostPeerandWorkerPool.
Code generation: stdin (CodeGeneratorRequest) -> request_reader.parseCodeGeneratorRequest() -> Generator.generateFile() -> StructGenerator.generate() -> stdout (.zig files)
Serialization: MessageBuilder.allocateStruct() -> StructBuilder.write*() -> MessageBuilder.toBytes()
Deserialization: Message.init(bytes) -> Message.getRootStruct() -> StructReader.read*() (zero-copy, reads directly from wire bytes)
RPC call flow: Client builds Call message -> Peer serializes and queues write -> Transport sends via write thread -> remote Connection frames and parses -> Peer dispatches to server implementation -> Return message sent back
Exports: message, schema, reader, codegen, request, schema_validation, canonical, reflection, rpc, io_backend
capnpc-zig/
├── src/
│ ├── main.zig # Compiler plugin entry point
│ ├── lib.zig # Library exports
│ ├── serialization/
│ │ ├── message.zig # Wire format: segments, pointers, packing
│ │ ├── message/ # Sub-modules: struct/list builders & readers,
│ │ │ # any-pointer, clone helpers
│ │ ├── schema.zig # Schema type definitions (Node, Field, Type, Value)
│ │ ├── reader.zig # Convenience re-exports for generated readers
│ │ ├── request_reader.zig # CodeGeneratorRequest parser
│ │ └── schema_validation.zig # Schema validation and canonicalization
│ ├── capnpc-zig/
│ │ ├── generator.zig # Code generation driver
│ │ ├── struct_gen.zig # Struct field accessor generation
│ │ └── types.zig # Cap'n Proto -> Zig type mapping
│ ├── rpc/
│ │ ├── mod.zig # RPC public module
│ │ ├── capnp/
│ │ │ └── rpc.capnp # Canonical RPC schema copy
│ │ ├── wire/ # Framing and protocol defs
│ │ ├── caps/ # Cap tables, descriptors, lifecycle helpers
│ │ ├── promises/ # Promise pipeline and return routing
│ │ ├── transport/ # Binding, stream state, TCP/QUIC backends
│ │ │ ├── tcp/
│ │ │ └── quic/
│ │ ├── peer/ # Dispatch, call/return/forward/provide
│ │ │ ├── call/ # orchestration, capability lifecycle,
│ │ │ ├── return/ # embargo, third-party handoff
│ │ │ ├── forward/
│ │ │ ├── provide/
│ │ │ └── third_party/
│ │ └── integration/ # HostPeer and WorkerPool adapters
│ └── wasm/ # Experimental WASM host ABI
├── tests/
│ ├── serialization/ # Message, codegen, interop, schema tests
│ ├── rpc/ # RPC tests organized by domain
│ ├── golden/ # Golden codegen output (do not format)
│ ├── interop/ # Cross-language interop fixtures
│ ├── e2e/ # End-to-end test harness
│ ├── capnp_testdata/ # Official Cap'n Proto test fixtures
│ └── test_schemas/ # .capnp schemas used by tests
├── docs/ # Design docs and guides
├── vendor/ext/ # Vendored submodules (go-capnp, capnp_test)
├── build.zig # Zig build configuration
├── build.zig.zon # Zig package manifest
├── Justfile # Task automation
└── mise.toml # Environment configuration
The RPC runtime implements the Cap'n Proto RPC protocol over domain-shaped TCP
and optional QUIC transport modules, using std.Io with a concurrent read/write
transport layer.
Status: Wire format, codegen, interop, and the RPC runtime are complete;
production hardening is ongoing. See docs/stability.md for the per-module
stability matrix and CHANGELOG.md for what is changing now.
Canonical RPC schema source-of-truth copy: src/rpc/capnp/rpc.capnp.
For the public-surface alias cleanup, see
docs/rpc-migration-guide.md.
- Concurrent I/O: Each connection uses a dedicated writer thread and blocking reads. All runtime types are single-threaded unless explicitly documented.
- Capability-based security: Each connection maintains export and import tables tracking capabilities by ID with reference counting. The runtime sends
Releasewhen a refcount reaches zero. - Promise pipelining: Calls can be pipelined on promised answers before results arrive, reducing round trips.
- Structured peer orchestration: The
Peertype handles the full lifecycle -- call dispatch, return handling, embargo management, capability forwarding, and third-party handoff. - Backend-agnostic I/O: Every socket op flows through
std.Io, so the runtime is polymorphic over the concrete backend.std.Io.Threadedandstd.Io.Eventedare selected through the same helper when Zig exposes them for the target.
The RPC runtime accepts a std.Io value at every entry point (rpc.transport.tcp.Listener.init, rpc.transport.tcp.Connection.init, rpc.transport.tcp.Transport.init). To centralise backend selection, the library exports capnpc.io_backend:
Scan report · 2026-09-11
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review
0 comments
log in to comment.