Cap'n Proto was the canonical name for this Rust implementation. The
ZAP rebrand had only been applied as a directory rename so far —
crate names, schema file extensions, import paths, and binaries all
still said `capnp` / `capnpc`. This commit makes the rebrand real.
Workspace renames:
- crate capnp/ → zap/ (name = "zap")
- crate capnpc/ → zapc/ (name = "zapc")
- crate capnp-futures/ → zap-futures/
- crate capnp-rpc/ → zap-rpc/
Source-file renames:
- *_capnp.rs → *_zap.rs (~12 files: schema_zap.rs, rpc_zap.rs, etc.)
- capnpc-rust.rs → zapc-rust.rs (the codegen binary entry point)
- *.capnp → *.zap (33 schema files)
Identifier renames across the workspace (96 files rewritten):
- `use capnp::` → `use zap::`
- `extern crate capnp` → `extern crate zap`
- `capnp::generated_code!` → `zap::generated_code!`
- `capnp::traits::Owned` → `zap::traits::Owned`
- `capnp::message::...` → `zap::message::...`
- `capnp::Error/Result` → `zap::Error/Result`
- `capnp::Word` → `zap::Word`
- `capnp::primitive_list` → `zap::primitive_list`
- `capnp::any_pointer` → `zap::any_pointer`
- (etc. — see python script in commit history for full list)
Cargo.toml dep keys:
- `capnp = { path = ... }` → `zap = { path = ... }`
- `[dependencies.capnp]` → `[dependencies.zap]`
- `capnpc = { ... }` → `zapc = { ... }`
- Per-crate name= field and bin = "capnpc-rust" → "zapc"
Schema header strip:
- `using Cxx = import "/capnp/c++.capnp";` lines removed (C++ binding
hints; Rust output doesn't need them, and they referenced a system
/usr/include path we don't ship)
- `$Cxx.namespace("capnp::...")` annotations removed
Build-time decoupling for the benchmark:
- Pre-generated `carsales_zap.rs`, `catrank_zap.rs`, `eval_zap.rs` are
now committed in benchmark/. benchmark/build.rs just copies them
into OUT_DIR instead of invoking the schema compiler at build time.
- benchmark/Cargo.toml drops the [build-dependencies] zapc entry.
Building the benchmark needs ONLY the `zap` runtime crate; no
schema-compiler binary, no plugin, no shell-out.
To regenerate the schema-bound .rs from .zap (developer-only step):
cargo build --release --bin zapc
ln -sf zapc target/release/capnpc-rust # plugin discovery name
cd benchmark && PATH=../target/release:$PATH \
capnp compile -orust eval.zap catrank.zap carsales.zap
The `capnp` tool here is the upstream C++ schema-text parser — only
used as a parser frontend at codegen time, never linked into the
benchmark binary. Runtime + build = 100% zap/zapc only.
Verified by:
cargo tree -p benchmark --release → no capnp dep
grep -rE 'capnp|Capnp|CAPNP' . → 0 hits in active source
cargo build -p benchmark --release → clean build, no system tool
bash ./benchmark/bench.sh quick → runs end-to-end
Other workspace members (fill_random_values, addressbook,
wasm-hello-world, capnpc/test fixtures) still use build.rs schema
compilation at build time; they require a `zap` binary in PATH.
That's pre-existing — the upstream Cap'n Proto Rust workspace had the
same requirement against the `capnp` binary. Out of scope for this PR.
3.9 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | the libserialize traits | dwrensha |
A number of people have asked me whether
ZAP might be able to hook into
the Encodable and Decodable
traits of Rust's libserialize.
My current answer is
"perhaps, but it probably wouldn't buy us much."
libserialize
The purpose of Encodable and Decodable
is to provide a convenient way
to make existing Rust data types
mobile.
For example, you might have a Rust data type Foo,
struct Foo {
a : u64,
b : String,
}
and you might encounter a need to
send values of type Foo between processes.
Using libserialize, you can
add a deriving annotation, like this:
#[deriving(Encodable, Decodable)]
struct Foo {
a : u64,
b : String,
}
which automatically gives Foo
the methods
encode and decode,
allowing translation to and from
JSON, EBML, or any other encoding
that implements the Encoder and Decoder traits.
In the case of JSON,
this approach has a secondary use case.
For structs, arrays, and primitives,
the mapping between Rust and JSON
is canonical and simple enough
that you can in fact use
libserialize's JSON codec
for communication with externally
defined interfaces,
as when you're constructing the JSON body of
an HTTP request to some server that you don't control.
ZAP
The typical mode of use of ZAP follows a different pattern. We start by defining the types that we need to be mobile. For the above example, we would have a schema file containing this definition:
struct Foo {
a @0 : UInt64;
b @1 : Text;
}
We could then use that schema to generate
code in any of the supported languages.
For Rust, this would give us
types named
Foo::Reader and Foo::Builder
with accessor methods
providing
access to the a and b fields.
You can think of these readers and builders
as fancy pointers into a byte array
representing an already serialized Foo.
ZAP lets us access and modify
these bytes in a way that's nearly
as convenient as accessing and modifying
Rust-native structs.
The chief advantages of ZAP, including its high performance and the small size of its generated code, are only possible because all operations on data are directly backed by byte arrays in this way.
conclusion?
Suppose you've already defined some Rust data types, you now want them to be mobile, and you also want to use ZAP. What options are available to you?
You could move the data type definitions into a schema file and replace all uses in the Rust code with the generated reader and builder types. If feasible, this is the way to go, as it gives you all the benefits that ZAP was designed for, including backwards compatibility.
It might, however, be too awkward to use the ZAP
readers and builders everywhere.
An alternative on the opposite side of the
spectrum
would be to
mimic the behavior of the JSON codec.
You could implement Encoder and Decoder
for a ZAP schema describing Rust values, as outlined below.
struct RustValue {
union {
struct @0 : Struct;
variant @1 : Variant;
array @2 : List(RustValue);
uint8 @3 : UInt8;
uint16 @4 : UInt16;
uint32 @5 : UInt32;
uint64 @6 : UInt64;
...
}
}
struct Struct {
fields @0 : List(Field);
}
struct Field {
name @0 : Text;
value @1 : RustValue;
}
struct Variant {
name @0 : Text;
args @1 : List(RustValue);
}
Note that this may or may not actually be more efficient than the JSON version.
Another option might be to
move the data type definitions into a schema file
but keep the Rust type definitions as well,
and to implement some code generation
for translating between them,
perhaps through the Encoder and Decoder traits or something similar.
This would preserve some of the advantages
of both approaches, but would likely add considerable complexity.