What is Cap'n Proto?
Cap'n Proto is a data interchange format and RPC system created by Kenton Varda, the author of Protocol Buffers v2. It was designed from the ground up to eliminate the encode/decode overhead that makes traditional serialization formats slow.
The key innovation is zero-copy reads: Cap'n Proto stores data directly in its wire format inside memory. Reading a field is a single pointer dereference — there is no deserialization step. Writing is direct memory layout — there is no serialization step either.
Cap'n Proto also includes a full RPC system with promise pipelining, capability references, and three-party handoff — features not present in FlatBuffers or Protocol Buffers.
CLib package — Requires libcapnp-dev. Ubuntu/Debian: sudo apt install libcapnp-dev
Performance Comparison
Because Cap'n Proto uses the wire format as the in-memory representation, encoding and decoding approach zero nanoseconds. Compare to formats that require a full object graph construction on every read or write:
| Format | Encode | Decode | Zero-copy | RPC |
| Cap'n Proto |
~0 ns (memory layout) |
~0 ns (pointer cast) |
Yes |
Built-in |
| FlatBuffers |
~0 ns (memory layout) |
~0 ns (pointer cast) |
Yes |
None |
| Protocol Buffers |
~100–300 ns/msg |
~50–200 ns/msg |
No |
gRPC (separate) |
| MessagePack |
~80–200 ns/msg |
~60–180 ns/msg |
No |
None |
| JSON |
>1 μs/msg |
>1 μs/msg |
No |
None |
Zero-copy detail: Cap'n Proto messages are kept in their wire format in RAM. Passing a message between threads or processes requires no copy — share the memory pointer. FlatBuffers shares this property but omits RPC. Protobuf always materialises a full object graph on decode.
Schema Language
Cap'n Proto schemas are defined in .capnp files. Each file carries a 64-bit ID (generated by capnp id). Fields are numbered from @0 — numbers are stable identifiers that allow forward/backward compatible evolution.
@0xdeadbeefdeadbeef; # 64-bit file ID (generate with: capnp id)
struct Person {
name @0 :Text;
age @1 :UInt32;
email @2 :Text;
}
struct AddressBook {
people @0 :List(Person);
}
Compile the schema to a binary blob and to C++ glue with:
# Compile to binary (used at runtime by LoadSchema)
capnp compile -o /dev/null --src-prefix=. schema.capnp # validates
capnp compile -oc++ schema.capnp # emits schema.capnp.h + .c++
Field numbers are forever. Once a field is assigned @N, never reuse that number. Add new fields with the next available number. Absent fields return typed defaults — they are never an error.
Types
| Type | Underlying | Description |
| TCapnSchema | Integer (handle) | Compiled schema handle returned by LoadSchema. Holds type metadata for all structs in the file. |
| TCapnMessage | Integer (handle) | An abstract Cap'n Proto message (parent of builder and reader). |
| TCapnBuilder | Integer (handle) | Message builder — used for writing. Allocate with NewBuilder, free with FreeBuilder. |
| TCapnReader | Integer (handle) | Message reader — used for reading serialized bytes. Allocate with NewReader, free with FreeReader. |
| TCapnStruct | Integer (handle) | A struct builder or reader within a message. Returned by InitRoot / GetRoot. |
| TCapnList | Integer (handle) | A list builder or reader within a struct. |
| TCapnSegment | record | Raw segment descriptor with Data: string (raw bytes) and Words: Int64 (size in 64-bit words). |
Schema API
| Function | Signature | Description |
| LoadSchema | LoadSchema(Path: string): TCapnSchema | Load a compiled .capnp binary schema file from disk. Returns a schema handle used by InitRoot. |
| LoadSchemaBytes | LoadSchemaBytes(Data: string): TCapnSchema | Load schema from an in-memory byte string (for embedded/bundled schemas). |
| FreeSchema | FreeSchema(S: TCapnSchema) | Release the schema handle and all associated memory. |
Building (Writing)
| Function | Signature | Description |
| NewBuilder | NewBuilder: TCapnBuilder | Allocate a new message builder. |
| FreeBuilder | FreeBuilder(B: TCapnBuilder) | Release all memory held by the builder. Always call after Serialize. |
| InitRoot | InitRoot(B: TCapnBuilder; S: TCapnSchema; TypeName: string): TCapnStruct | Initialise the root struct. TypeName must match a struct name in the schema (e.g. 'Person'). |
Struct field setters
| Function | Signature | Description |
| SetText | SetText(Struct: TCapnStruct; FieldIndex: Integer; Value: string) | Set a Text field at the given 0-based field index. |
| SetInt32 | SetInt32(Struct: TCapnStruct; FieldIndex, Value: Integer) | Set a 32-bit signed integer field. |
| SetInt64 | SetInt64(Struct: TCapnStruct; FieldIndex: Integer; Value: Int64) | Set a 64-bit signed integer field. |
| SetUInt32 | SetUInt32(Struct: TCapnStruct; FieldIndex: Integer; Value: Int64) | Set a UInt32 field (passed as Int64 to avoid sign issues). |
| SetFloat64 | SetFloat64(Struct: TCapnStruct; FieldIndex: Integer; Value: Double) | Set a Float64 field. |
| SetBool | SetBool(Struct: TCapnStruct; FieldIndex: Integer; Value: Boolean) | Set a Bool field. |
| SetData | SetData(Struct: TCapnStruct; FieldIndex: Integer; Value: string) | Set a Data (raw bytes) field. |
| InitStruct | InitStruct(Struct: TCapnStruct; FieldIndex: Integer): TCapnStruct | Initialise a nested struct field and return its handle for further setters. |
| InitList | InitList(Struct: TCapnStruct; FieldIndex, Count: Integer): TCapnList | Initialise a list field with Count pre-allocated slots. |
Serialization
| Function | Signature | Description |
| Serialize | Serialize(B: TCapnBuilder): string | Serialize to standard flat framing format. Suitable for files, sockets, and IPC. |
| SerializePacked | SerializePacked(B: TCapnBuilder): string | Serialize to packed format. Runs of zero words are compressed (~50% savings for sparse structs). |
Reading
| Function | Signature | Description |
| NewReader | NewReader(Data: string): TCapnReader | Create a reader from bytes produced by Serialize. |
| NewReaderPacked | NewReaderPacked(Data: string): TCapnReader | Create a reader from bytes produced by SerializePacked. |
| FreeReader | FreeReader(R: TCapnReader) | Release all memory held by the reader. |
| GetRoot | GetRoot(R: TCapnReader): TCapnStruct | Get the root struct handle for reading fields. |
Struct field getters
| Function | Signature | Description |
| GetText | GetText(Struct: TCapnStruct; FieldIndex: Integer): string | Read a Text field. Returns empty string if absent. |
| GetInt32 | GetInt32(Struct: TCapnStruct; FieldIndex: Integer): Integer | Read a 32-bit signed integer field. |
| GetInt64 | GetInt64(Struct: TCapnStruct; FieldIndex: Integer): Int64 | Read a 64-bit signed integer field. |
| GetUInt32 | GetUInt32(Struct: TCapnStruct; FieldIndex: Integer): Int64 | Read a UInt32 field (returned as Int64). |
| GetFloat64 | GetFloat64(Struct: TCapnStruct; FieldIndex: Integer): Double | Read a Float64 field. |
| GetBool | GetBool(Struct: TCapnStruct; FieldIndex: Integer): Boolean | Read a Bool field. |
| GetData | GetData(Struct: TCapnStruct; FieldIndex: Integer): string | Read a Data (raw bytes) field. |
| GetStruct | GetStruct(Struct: TCapnStruct; FieldIndex: Integer): TCapnStruct | Read a nested struct field. |
| GetList | GetList(Struct: TCapnStruct; FieldIndex: Integer): TCapnList | Read a list field. |
List operations
| Function | Signature | Description |
| ListSize | ListSize(L: TCapnList): Integer | Return the number of elements in the list. |
| ListGetText | ListGetText(L: TCapnList; Index: Integer): string | Get the Text element at the given 0-based index. |
| ListSetText | ListSetText(L: TCapnList; Index: Integer; Value: string) | Set the Text element at the given index (builder only). |
| ListGetStruct | ListGetStruct(L: TCapnList; Index: Integer): TCapnStruct | Get the struct element at the given index for further field access. |
Code Example — Write and Read a Person Message
uses capnproto;
var
Schema : TCapnSchema;
B : TCapnBuilder;
Root : TCapnStruct;
Data : string;
R : TCapnReader;
begin
{ Load the compiled schema binary }
Schema := LoadSchema('/path/to/person.capnp.bin');
{ ── Write ───────────────────────────────────────── }
B := NewBuilder;
Root := InitRoot(B, Schema, 'Person');
SetText(Root, 0, 'Alice'); { name @0 }
SetUInt32(Root, 1, 30); { age @1 }
SetText(Root, 2, 'alice@example.com'); { email @2 }
Data := Serialize(B); { wire format — zero-copy layout }
FreeBuilder(B);
{ ── Read ────────────────────────────────────────── }
R := NewReader(Data);
Root := GetRoot(R);
WriteLn(GetText(Root, 0)); { Alice }
WriteLn(GetUInt32(Root, 1)); { 30 }
WriteLn(GetText(Root, 2)); { alice@example.com }
FreeReader(R);
FreeSchema(Schema);
end.
Field indices match the schema. SetText(Root, 0, ...) writes field @0, SetUInt32(Root, 1, ...) writes field @1, and so on. There is no name lookup at runtime — it is a direct memory offset computation.
Cap'n Proto vs Protocol Buffers
Both formats are schema-driven and produce compact binary output. The fundamental difference is the memory model:
| Cap'n Proto | Protocol Buffers |
| Memory model | Wire format = in-memory format | Wire format decoded into separate objects |
| Encode cost | ~0 ns | ~100–300 ns / message |
| Decode cost | ~0 ns | ~50–200 ns / message |
| RPC system | Built-in (promise pipelining) | Separate (gRPC) |
| Language support | C++, Python, Java, Go, Rust… | 20+ languages, wider ecosystem |
| Tooling maturity | Smaller community | Dominant in industry |
When to choose Cap'n Proto: performance-critical paths, high-frequency IPC, game state, low-latency networking, or when built-in RPC pipelining is needed.
When to choose Protobuf: maximum language ecosystem (20+ languages), large existing infrastructure, or gRPC compatibility.
Utility
| Function | Signature | Description |
| CapnProtoVersion | CapnProtoVersion: string | Returns the version string of the underlying libcapnp (e.g. '0.10.4'). |
← Back to Package Index