ZAP Protocol
SDKs

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` binary

The 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/zapc

Runtime — 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 is zap, so you use zap::cap::… after zap-schema = "1" (or the workspace zap crate, which re-exports the cap types as zap::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 UB
  • no_std and no-alloc support
  • Run-time reflection

Build

cargo build --release

The 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

On this page