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.6 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | 0.18 — lazy UTF-8 and no-alloc | dwrensha |
New release alert! Version 0.18 of zap-rust is now available on crates.io.
If you use zap-rust on data with
the Text built-in type,
then it's likely that this release will require some
updates to your code.
But don't worry — the changes are straightforward and they bring some
important benefits.
lazy UTF-8 validation
Suppose we have the following struct defined in a ZAP schema:
struct Foo {
oneText @0 :Text;
anotherText @1 :Text;
}
Then, in Rust, these Text fields can be accessed through the text::Reader type:
let my_foo: foo::Reader = ...;
let one_text: zap::text::Reader<'_> = my_foo.get_one_text()?;
let another_text: zap::text::Reader<'_> = my_foo.get_another_text()?;
But what exactly is a text::Reader?
the old definition
In previous versions of zap-rust, the text::Reader type
was an alias to Rust's &str type:
pub mod text {
type Reader<'a> = &'a str;
}
At first glance, this seems like a perfect fit.
A ZAP Text value is required to
contain valid UTF-8 data, just like a Rust &str,
and a text::Reader is meant to represent
a reference to that data.
However, in practice, there are some ways in which this representation falls short.
-
performance: Validating UTF-8 data has a cost, and ideally we would like to avoid paying it multiple times on the same data. If
text::Readeris just&str, then we need to validate every time that we:- copy a text field from one message to another,
- write a text field to a file,
- write a file to a text field, or
- access some sub-range of a text field.
This goes against the general ZAP philosophy of doing validation as lazily as possible.
-
robustness -- If a text field holds corrupted data, then you still might want to be able to access that data, even if it is not valid UTF-8. For example, imagine that a text field holds log messages from a web server. We should still be able to read the messages, even if they are garbled. Indeed, garbled messages are probably the most interesting ones, as they indicate unexpected behavior. See this issue for more discussion.
the new definition
To address the above-noted shortcomings,
version 0.18 of zap-rust defines text::Reader
like this:
pub mod text {
/// Wrapper around utf-8 encoded text.
/// This is defined as a tuple struct to allow pattern matching
/// on it via byte literals (for example `text::Reader(b"hello")`).
#[derive(Copy, Clone, PartialEq)]
pub struct Reader<'a>(pub &'a [u8]);
impl<'a> Reader<'a> {
pub fn as_bytes(self) -> &'a [u8] { ... }
pub fn to_str(self) -> Result<&'a str, Utf8Error> { ... }
pub fn to_string(self) -> Result<String, Utf8Error> { ... }
}
impl<'a> From<&'a str> for Reader<'a> { ... }
impl<'a> From<&'a [u8]> for Reader<'a> { ... }}
}
Now consumers can easily access the underlying data, via as_bytes(),
and getting it as a &str or String just requires an extra to_str()
or to_string() call.
When setting text fields in a message, you will now need to
insert some .into() calls to convert from a str or String
into a text::Reader, like this:
let name: &str = "alice";
let mut my_foo: foo::Builder = ...;
my_foo.set_one_text("hello world".into())?;
my_foo.set_another_text(format!("hello {name}")[..].into())?;
All this is admittedly more verbose than it was before, but it's in keeping with the general spirit of zap-rust: we are willing to introduce some verbosity if that's what it takes to model ZAP data in a satisfactory way.
no-alloc mode
Another new feature is no-alloc mode.
In version 0.13, zap-rust
gained support for no_std environments.
However, it still depended on the alloc crate,
which can sometimes be a problem for microcontroller targets and kernel programming.
(See this issue
for some discussion.)
Starting with version 0.18, the zap crate now has an
alloc Cargo feature,
which can be disabled to remove the alloc dependency.
A side benefit of this change is that now error handling in zap-rust is much less dependent on heap allocation, and so should have better performance and be more reliable.