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.3 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | 0.19 — ergonomic setters and faster reflection | dwrensha |
As of today, version 0.19 of zap-rust is available on crates.io.
This release includes improved ergnomics and performance, while also having a notable breaking change involving text fields.
setter ergonomics
Suppose that we have the following struct defined in a ZAP schema file:
struct Cookie {
fortune @0 :Text;
numbers @1 :List(UInt16);
}
With zap-v0.18.0 (the previous release), to populate such a struct you would write Rust code like this:
let mut message = zap::message::Builder::new_default();
let mut root: cookie::Builder = message.init_root();
root.set_fortune("This too shall pass.".into());
let mut numbers = root.init_numbers(6);
numbers.set(0, 4);
numbers.set(1, 8);
numbers.set(2, 15);
numbers.set(3, 16);
numbers.set(3, 23);
numbers.set(3, 42);
This is rather more verbose than you might hope.
The setter methods set_fortune() and set_numbers() are geared toward
accepting input from other ZAP messages, rather than
from Rust-native values.
When we want to call set_fortune() on a Rust-native &str,
we first need to convert it into a zap::text::Reader via the .into() method.
Similarly, the set_numbers() method wants a primitive_list::Reader<u16>,
and there is no easy way for us to get one of those from a Rust-native &[u16].
Therefore, we avoid that method altogether, and instead opt to use init_numbers()
and to invidually set each element of the list.
In zap-v0.19.0, we can instead directly set these fields from Rust-native values:
let mut message = zap::message::Builder::new_default();
let mut root: cookie::Builder = message.init_root();
root.set_fortune("This too shall pass.");
root.set_numbers(&[4, 8, 15, 16, 23, 42]);
This is possible because the setter methods have been generalized
to accept a value of type impl SetterInput<T>, as follows:
mod cookie {
impl <'a> Builder<'a> {
pub fn set_fortune(&mut self, impl SetterInput<zap::text::Owned>) {
...
}
pub fn set_numbers(&mut self,
impl SetterInput<zap::primitive_list::Owned<u16>>) {
...
}
}
}
The trait SetterInput<zap::text::Owned> is implemented both by
zap::text::Reader and by &str, and
the trait SetterInput<zap::primitive_list::Owned<u16>>
is implemented by both zap::primitive_list::Reader<u16>
and by &[u16].
breaking change
Unfortunately, this generalization does cause some breakage. If we did not update the old line
root.set_fortune("This too shall pass.".into());
then it would now gives us a type error:
error[E0283]: type annotations needed
...
= note: multiple `impl`s satisfying `_: SetterInput<zap::text::Owned>` found in the `zap` crate:
- impl<'a> SetterInput<zap::text::Owned> for &'a String;
- impl<'a> SetterInput<zap::text::Owned> for &'a str;
- impl<'a> SetterInput<zap::text::Owned> for zap::text::Reader<'a>;
note: required by a bound in `cookie::Builder::<'a>::set_fortune`
The problem is that .into() does not know which type to target.
The fix is to remove the .into().
Note that the need for such .into() calls was in fact only recently
introduced, in the release of
version 0.18.
Probably we should have
delayed that release until we had a solution like
the present impl SetterInput generalization,
thereby minimizing the churn of downstream code.
faster reflection
The 0.17 release
added support for run-time reflection,
including a DynamicStruct type that supports
looking up fields by name.
The initial implementation
worked by linearly scanning a struct's fields.
That works fine for small structs, but can
get expensive when there are a large number of fields.
In #469, @quartox updated the implementation to use binary search, resulting in a significant performance increase, and matching the zap-c++ implementation.
This change involved add a new field to the static RawStructSchema value included
in the generated code for each ZAP type.