Rust
ZAP Rust SDK — the zero-copy runtime, zapc codegen, Level-1 capability RPC, and the cap layer.
Rust Binding
The Rust implementation of ZAP is a Cargo workspace
(zap-proto/rust) providing the zero-copy
message runtime, the Rust code-generation backend, and a Level-1 capability RPC
stack. The schema compiler ships separately as the
zap-schema crate, which also carries the
capability runtime (zap::cap).
ZAP describes data and interfaces in a whitespace-significant schema — no braces, no required byte-offsets; blocks are delimited by indentation and ordinals are assigned in declaration order. The legacy brace form still parses for backward compatibility.
Install
The schema compiler and the runtime are obtained separately.
Schema compiler — the CLI that turns a .zap file into Rust:
cargo install zap-schema # provides the `zapc` binaryThe zap-schema crate bundles the canonical schema front-end (the same parser
every other language plugin uses, built from
zap-proto/cpp-core) together with the
Rust backend, so no external C++ toolchain is required at build time. The npm
wrapper @zap-proto/zapc
exposes the same backend for projects already on a Node toolchain — it downloads
a prebuilt, checksum-verified binary on install:
npm install -g @zap-proto/zapcRuntime — add the workspace crates you need to Cargo.toml:
[dependencies]
zap = { git = "https://github.com/zap-proto/rust" } # zero-copy messages
zapc = { git = "https://github.com/zap-proto/rust" } # build.rs codegen hook
zap-rpc = { git = "https://github.com/zap-proto/rust" } # capability RPC
zap-futures = { git = "https://github.com/zap-proto/rust" } # async read/write
tokio = { version = "1", features = ["full"] }Codegen
To generate code as part of a cargo build, call zapc from build.rs:
fn main() {
zapc::CompilerCommand::new()
.file("schema/hello_world.zap")
.run()
.expect("zap schema compile");
}zapc::CompilerCommand execs the canonical zap schema front-end and feeds its
code-generator request to the Rust backend — the whitespace grammar (and its
brace back-compat) is handled entirely by that front-end, so there is exactly one
schema parser across the whole stack.
For this schema, the generated code includes a point::Reader<'a> with
get_x() / get_y(), a point::Builder<'a> with set_x() / set_y(), and —
for the interface — a point_tracker::Server trait and a point_tracker::Client:
struct Point
x Float32
y Float32
interface PointTracker
addPoint (p Point) -> (totalPoints UInt64)The 'a lifetime records that a reader/builder borrows the raw buffer holding
the encoded message — that buffer is never copied into a separate structure. The
encoding doubles as the in-memory representation, so reading a field is zero-copy.
RPC
zap-rpc is an object-capability RPC system with Level-1 promise pipelining.
Generated from an interface, a Server trait carries one async method per
declaration, and a Client carries one *_request() builder per method. For
this schema:
interface HelloWorld
sayHello (request HelloRequest) -> (reply HelloReply)Server
Implement the generated Server trait, then hand an instance to
zap_rpc::new_client and serve it over a transport.
use zap_rpc::{rpc_twoparty_zap, twoparty, RpcSystem};
use crate::hello_world_zap::hello_world;
use futures::AsyncReadExt;
struct HelloWorldImpl;
impl hello_world::Server for HelloWorldImpl {
async fn say_hello(
self: std::rc::Rc<Self>,
params: hello_world::SayHelloParams,
mut results: hello_world::SayHelloResults,
) -> Result<(), ::zap::Error> {
let name = params.get()?.get_request()?.get_name()?.to_str()?;
results.get().init_reply().set_message(format!("Hello, {name}!"));
Ok(())
}
}
// Bind a listener and serve the bootstrap capability:
let hello_world_client: hello_world::Client = zap_rpc::new_client(HelloWorldImpl);
let (stream, _) = listener.accept().await?;
let (reader, writer) =
tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split();
let network = twoparty::VatNetwork::new(
futures::io::BufReader::new(reader),
futures::io::BufWriter::new(writer),
rpc_twoparty_zap::Side::Server,
Default::default(),
);
let rpc_system =
RpcSystem::new(Box::new(network), Some(hello_world_client.client));
tokio::task::spawn_local(rpc_system);Client
Bootstrap the remote interface, build a request, and await the promise.
let mut rpc_system = RpcSystem::new(rpc_network, None);
let hello_world: hello_world::Client =
rpc_system.bootstrap(rpc_twoparty_zap::Side::Server);
tokio::task::spawn_local(rpc_system);
let mut request = hello_world.say_hello_request();
request.get().init_request().set_name("world");
let reply = request.send().promise.await?;
println!("{}", reply.get()?.get_reply()?.get_message()?.to_str()?); // Hello, world!Because say_hello_request() returns a request whose result can be used as the
target of another call before it resolves, dependent calls pipeline into a single
round trip — that is the Level-1 feature.
Capabilities
The zap-schema crate's cap module is
the capability runtime. A Capability is a signed, attenuable token of authority
over a target, granted to a holder by an issuer; caps form a chain via the
parent field, and Verifier::verify_chain walks back to a root checking each
signature, expiry, revocation, target invariance, and monotonically-narrowing
permissions.
use zap::cap::{issue, Issuance, Verifier, Ed25519Signer, CapKind, Caveat, CaveatKind};
// Mint a root cap. Ed25519Signer is the built-in mandatory scheme; ML-DSA-65 is
// also provided, and a SchemeVerify hook wires hybrid / secp256k1.
let signer = Ed25519Signer::generate();
let cap = issue(
Issuance {
kind: CapKind::IamSession.value(),
target, // [u8; 32] resource hash
holder, // [u8; 32] holder hash
permissions: 0xDEAD_BEEF_CAFE_BABE, // bits the holder may exercise
expires_at: 2_000_000_000, // unix seconds; 0 = never
caveats: vec![
Caveat::new(CaveatKind::IpCidr, b"10.0.0.0/8".to_vec()),
],
..Default::default()
},
&signer,
)?;
// Verify a single cap. The Verifier carries the policy as builder closures.
let verifier = Verifier::new()
.with_issuer_key(move |_issuer| Ok(signer.public_key().to_vec()));
verifier.verify(&cap, 1_700_000_000)?; // Err(CapError::Expired) / Revoked / SigMismatch / …Derive a narrower child with Capability::attenuate; the parent must carry
PermAttenuate (or be a CapKind::Delegate cap), permissions intersect, and
expiry can only shrink. cap.cap_id() is SHA-256(canonical_bytes ‖ Sig) and
cap.canonical_bytes() is the SPEC §3 signed scope — both byte-identical across
language runtimes. verify_chain(leaf, chain, op, target, holder, now) validates
a full chain.
The crate name on crates.io is
zap-schema; its library name iszap, so youuse zap::cap::…afterzap-schema = "1"(or the workspacezapcrate, which re-exports the cap types aszap::cap_issue,zap::Capability, etc.).
Features
- Tagged unions, generics, and forward-compatible protocol evolution
- Canonicalization for deterministic, signable encodings
Result-based error handling — invalid pointers surface as errors, never UBno_stdand no-alloc support- Run-time reflection
Build
cargo build --releaseThe library crates (zap, zapc, zap-rpc, zap-futures) build with no
external tooling. The example, test, and benchmark crates additionally need the
zap schema front-end on PATH (CI provisions it by building zap_tool from
zap-proto/cpp-core).
Examples
- zap-rpc/examples/hello-world — the generated client/server above, end to end
- zap-rpc/examples — calculator, pubsub, streaming, reconnect
- example/addressbook — serialization round-trip