wireform-capnproto
wireform-capnproto implements Cap’n Proto, Kenton
Varda’s zero-copy serialization framework. Cap’n Proto splits structs into a fixed
data section (scalars packed by size) and a pointer section (text, lists, nested
structs), making buffers directly mappable for read-heavy workloads. Use this
package when you need mmap-friendly serialization with strict schema evolution
rules, or when integrating with Cap’n Proto services and .capnp schema files.
Key features
Section titled “Key features”- Typeclass API via
ToCapnProtoandFromCapnProtowith Template Haskell deriving - Cap’n Proto IDL parser and codegen from
.capnpschema files - Segment-based wire layout with separate data and pointer sections
- Zero-copy-oriented decode that reconstructs values from mapped buffers
- QuasiQuoter for inline
[capnp| ... |]schemas - Runtime registry for struct schema lookup
Basic usage
Section titled “Basic usage”For typed records, derive instances and round-trip through the segment encoder:
{-# LANGUAGE DerivingStrategies #-}{-# LANGUAGE TemplateHaskell #-}
import CapnProto.Decode qualified as CPDimport CapnProto.Derive (ToCapnProto (..), FromCapnProto (..), deriveCapnProto)import CapnProto.Encode qualified as CPEimport Data.Text (Text)import Data.Word (Word32)import GHC.Generics (Generic)
data Person = Person { personName :: !Text , personAge :: !Word32 } deriving stock (Show, Eq, Generic)
$(deriveCapnProto ''Person)
encodePerson :: Person -> ByteStringencodePerson = CPE.encode . toCapnProto
decodePerson :: ByteString -> Either String PersondecodePerson bs = do val <- CPD.decode bs fromCapnProto val
bob :: Personbob = Person "Bob" 42
roundTrip :: Either String PersonroundTrip = decodePerson (encodePerson bob)You can also work directly with the dynamic Value ADT when exploring wire
layout or bridging between schemas:
import qualified Data.Vector as Vimport qualified CapnProto.Value as CP
manualStruct :: CP.ValuemanualStruct = CP.Struct (V.fromList [CP.UInt32 42]) -- data section (V.fromList [CP.Text "hello capnp"]) -- pointer section
manualBytes :: ByteStringmanualBytes = CPE.encode manualStructGenerate types from .capnp files with the quasiquoter or CLI:
{-# LANGUAGE TemplateHaskell #-}import CapnProto.QQ (capnp)
[capnp| struct Person { name @0 :Text; age @1 :UInt32; }|]wireform-gen capnp -i schema.capnp -o src/Gen/Performance
Section titled “Performance”Encode/decode (zero-copy decode)
Section titled “Encode/decode (zero-copy decode)”| Operation | encode | decode | ratio |
|---|---|---|---|
| Person struct | 114 ns | 28.4 ns | 0.25x |
| Person[100] | 8610 ns | 28.0 ns | 0.00x |
Last run 2026-06-27 11:35:54 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5. Decode is a zero-copy cursor by design: only the outer envelope is resolved at decode time. Per-field reads happen lazily..
Decode is effectively O(1) regardless of payload size because Cap’n Proto uses zero-copy cursors: only the outer envelope is resolved at decode time, and per-field reads happen lazily on access. Encode is proportional to message size.
The chart and table above are regenerated by wireform-stats from wireform-capnproto/bench-results/summary/capnproto-encode-decode.json — the same source the README chart is built from.
Notable modules
Section titled “Notable modules”| Module | Purpose |
|---|---|
CapnProto.Derive | ToCapnProto / FromCapnProto and deriveCapnProto |
CapnProto.Encode / CapnProto.Decode | Segment encoder and decoder |
CapnProto.Value | Dynamic untyped Value ADT (data + pointer sections) |
CapnProto.Schema / CapnProto.Parser | Schema AST and .capnp parser |
CapnProto.CodeGen / CapnProto.QQ | Haskell codegen and quasiquoter |
CapnProto.Registry | Runtime struct schema registry |