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.
4.2 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | RPC system rewritten to use async I/O | dwrensha |
Today I've released verson 0.6 of the three main ZAP crates: the runtime, the code generator, and the remote procedure call system.
The biggest change is that the RPC system is now asynchronous, built on top of the GJ event loop library.
promises
ZAP's RPC protocol allows method calls to be made on distributed objects. It has a built-in notion of pipelining that can minimize network round-trips, and it allows object references to be transmitted within messages, alongside plain-old-data.
Such a system has an inherent need to deal with concurrency, both in its internal implementation in its publicly-exposed interfaces.
For example, it's common for a user to want to implement an
RPC method that makes a bunch of other RPC calls
and then collects their results
before returning.
How should these actions compose?
In the new version of zap-rpc,
which uses GJ's Promise abstraction,
each of the inner method calls
returns a promise,
and those promises can be collected with Promise::all()
to form a new promise which can then be returned from the outer method.
To see more concretely what this kind of thing looks like, take a look at the calculator example, which also showcases some fancier features. There are also some more-practical examples in the form of Sandstorm apps: a simple GET/PUT/DELETE server and a word game.
error handling
A Promise<T, E> is essentially a deferred Result<T, E>,
so it should be no surprise that
today's release pertains to
our continuing
story
about error handling.
Last time, we described our switch to using Result<T,E> pervasively,
so that we could return an Err(e) on a decode error,
rather than panicking.
That switch had some costs:
- We now need to sprinkle
try!()s in our code, one for any time we dereference a ZAP pointer. - We also need to edit some return types from
TtoResult<T, E>, and in some cases need to define helper functions so thattry!()has a place to return to.
In my opinion, (1) is not so bad, and it has the advantage of making control flow more clear.
The proposed ? operator
would make this even nicer.
In the asynchronous world of ZAP RPC,
(2) becomes less of a hassle, because
most functions that need to read a ZAP message
are asynchronous, and therefore already return a Promise<T, E>.
In such cases, we can use the pry!()
macro that GJ defines. The pry!() macro acts like try!(), but in the early return case returns
Promise::err(e) rather than Result::Err(e).
a simpler error type
One error-related question that often arises when designing interfaces that use Promise<T,E> is:
what concrete error type should be plugged in for E?
In previous versions of zap-rust, I had defined an error enum zap::Error
with various cases, one of which wrapped a std::io::Error.
This got me into trouble when I wanted to start using
Promise::fork(),
which requires that E be Clone.
The problem is that std::io::Error is not Clone!
To address this problem, I have redefined ::zap::Error to follow the design of
kj::Exception.
It's now a very simple struct with a String description and an ErrorKind enum,
where the only variants of
ErrorKind are Failed, Disconnected, Overloaded, and Unimplemented.
The observation here is that there seems to be very little gained by
defining hierarchies of errors wrapping other errors.