Files
zeekay f15816a8b2 docs: rewrite Go/Python/Rust/C++ SDK + quick-start against real runtime
Replace the fictional MCP-style tool-calling API (Zap.connect / call_tool /
Gateway / ToolCall) and wrong package names with the verified zero-copy wire +
capability RPC surface, every name checked against source.

- go.mdx: github.com/zap-proto/[email protected]. Builder→Parse round-trip, zapgen
  codegen (NewPing/WrapPing/PingInput), the rpc call envelope
  (BuildRequest/ParseResponse + 3-value pipelining client), and the cap layer
  (Issue/Attenuate/Verifier.Verify). Compiled+ran a generated Calculator
  client/server end to end (prints 15).
- python.mdx: PyPI zap-proto, import zap (not zap_protocol). Wire round-trip,
  zap.rpc (Server/Client/Method — ran end to end), zap.cap (issue/attenuate/
  Verifier — ran issue+verify), and the [app] decorator app (ZAP + Client).
- rust.mdx: zap-schema crate (zapc bin) + the zap-proto/rust workspace crates
  (zap/zapc/zap-rpc/zap-futures). Real zap-rpc Server trait + RpcSystem client
  + zap::cap (issue/Verifier). Drops nonexistent zap-proto/zap-codegen crates,
  spec-rs, zap_protocol module.
- cpp.mdx: separates the cpp-core schema front-end + zero-copy zap:: runtime
  (MallocMessageBuilder / messageToFlatArray / TwoPartyClient) from the cpp
  high-level binding; drops the fictional zap::connect/callTool client.
- quick-start.mdx: real install/codegen (zapgen, cargo install zap-schema,
  @zap-proto/zapc — not zap-codegen / @zap-proto/codegen / spec/cmd/zap) and
  real generated-RPC server/client per language.
2026-06-21 01:34:27 -07:00

233 lines
8.5 KiB
Plaintext

---
title: Rust
description: 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`](https://github.com/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`](https://crates.io/crates/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:
```bash
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`](https://github.com/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`](https://www.npmjs.com/package/@zap-proto/zapc)
exposes the same backend for projects already on a Node toolchain — it downloads
a prebuilt, checksum-verified binary on install:
```bash
npm install -g @zap-proto/zapc
```
**Runtime** — add the workspace crates you need to `Cargo.toml`:
```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`:
```rust
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`:
```zap
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:
```zap
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.
```rust
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.
```rust
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`](https://crates.io/crates/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.
```rust
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
```bash
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
- [zap-rpc/examples/hello-world](https://github.com/zap-proto/rust/tree/main/zap-rpc/examples/hello-world) — the generated client/server above, end to end
- [zap-rpc/examples](https://github.com/zap-proto/rust/tree/main/zap-rpc/examples) — calculator, pubsub, streaming, reconnect
- [example/addressbook](https://github.com/zap-proto/rust/tree/main/example/addressbook) — serialization round-trip