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.8 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | lifetime variables and safety | dwrensha |
Like its C++ counterpart, zap-rust relies heavily on raw pointer manipulation to achieve good performance. In fact, the translation from C++ to Rust for the core low level data structures is quite direct, as you can see by comparing layout.c++ and layout.rs.
One important difference is that the Rust language, in addition to requiring that we explicitly label the pointer manipulations as "unsafe", also provides us with better facilities for building a safe external interface on top of them.
To illustrate, let's look at some ways one might misuse the C++ interface.
Here's a program that builds two
Person structs, each a part of
its own AddressBook message.
#include "addressbook.zap.h"
#include <zap/message.h>
#include <iostream>
using addressbook::Person;
using addressbook::AddressBook;
Person::Builder returnPersonBuilder(int id) {
::zap::MallocMessageBuilder message;
auto addressBook = message.initRoot<AddressBook>();
auto people = addressBook.initPeople(1);
people[0].setId(id);
people[0].setName("Alice");
return people[0];
}
int main(int argc, char* argv[]) {
auto person1 = returnPersonBuilder(123);
auto person2 = returnPersonBuilder(456);
std::cout << person1.getId() << "\n";
return 0;
}
You might expect the program to print
"123", but it actually prints "456".
The problem is that the Person::Builder returned
by the returnPersonBuilder() function
is unsafe to use because it
outlives its MessageBuilder.
Here is a snippet showing a related problem.
{
::zap::MallocMessageBuilder message;
auto addressBook = message.initRoot<AddressBook>();
auto people = addressBook.initPeople(1);
auto alice = people[0];
alice.setId(123);
auto person = message.initRoot<Person>();
std::cout << alice.getId() << "\n";
}
You might expect this code to print "123", but
it actually prints "0" because alice
is no longer valid after message has
been initialized a second time.
Both of these errors could be statically
detected and prevented in Rust.
The key is to arrange that the
MessageBuilder::initRoot() function
borrow a reference to the message that invokes it,
and to keep track of the lifetime of that borrow.
The Rust typechecker will then be able to detect
if the message is borrowed again
or if some sub-builder of it---whose type will
be annotated with the lifetime---outlives the
lifetime.
To make this concrete, in Rust
the signature of MessageBuilder::initRoot could look something like this:
pub fn initRoot<'a, T : FromStructBuilder<'a>>(&'a mut self) -> T;
where FromStructBuilder is the trait
pub trait FromStructBuilder<'a> {
fn fromStructBuilder(structBuilder : StructBuilder<'a>) -> Self;
}
and StructBuilder is a low-level type
which should not be exposed to the safe user interface.
Here 'a is the lifetime variable that tracks the borrow
of the message builder.
The generated code for AddressBook and Person will then
contain implementations for
the FromStructBuilder trait:
impl <'a> FromStructBuilder<'a> for AddressBook::Builder<'a> { ... }
impl <'a> FromStructBuilder<'a> for Person::Builder<'a> { ... }
Unfortunately, there's one hitch: the Rust compiler does not yet quite support this kind of interplay between lifetime variables and traits. This is why I have been so eagerly watching Rust issues 5121, 7331, and 10391.
In the meantime, zap-rust does partially enforce the kind of lifetime safety described above, but only for message readers, not message builders, and only using a somewhat roundabout strategy that makes it awkward to support the more complex ZAP types like lists of lists.