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.
5.4 KiB
layout, title, author
| layout | title | author |
|---|---|---|
| post | custom mutable reference types | dwrensha |
Rust's mutable references provide exclusive access to writable locations in memory.
If we have x : &'a mut T,
then we know that the referred-to T
cannot be read or modified except through dereferencing x.
In other words, x can have no aliases.
This guarantee is
crucial for memory safety,
as it implies that
any mutations we apply through
x have no risk of invalidating references held
elsewhere in our program.
The Builder types
of zap-rust
also need to provide an exclusivity guarantee.
Recall that if Foo is a struct defined in a ZAP schema,
then a foo::Builder<'a>
provides access to a writable location
in arena-allocated memory that contains
a Foo in ZAP format.
To protect access to that memory, a foo::Builder<'a> ought to behave
as if it were a &'a mut Foo,
even though the Foo type
cannot directly exist in Rust
(because ZAP struct layout
differs from Rust struct layout).
So the question arises: how do we define custom mutable references?
As we'll see, the easy part is ensuring exclusive access,
which can be achieved simply by not
implementing the Copy trait for foo::Builder<'a>.
The tricky part is making it ergonomic to reuse a reference,
something that built-in mutable references achieve through
special automatic reborrowing semantics. Our custom references
can use similar semantics, but they need to be slightly more explicit about it.
Okay, let's get concrete. Suppose that Foo is defined in a ZAP schema like this:
struct Foo {
x @0 : Float32;
blob @1 : Data;
}
When we call zap compile -orust foo.zap, we get generated code
containing the following definitions:
mod foo {
pub struct Builder<'a> {...}
impl <'a> Builder<'a> {
pub fn get_x(self) -> f32 {...}
pub fn set_x(&mut self, value : f32) {...}
pub fn get_blob(self) -> ::zap::data::Builder<'a> {...}
pub fn set_blob(&mut self, value : ::zap::data::Reader) {...}
pub fn init_blob(self, length : u32) -> ::zap::data::Builder<'a> {...}
...
}
...
}
You see here the usual accessor methods that allow us to
read and modify a Foo.
Note that the get_ and init_ methods take a by-value self
parameter.
This ensures that at most one ::zap::data::Builder referring to the blob field
can be obtained.
For example, if we call foo.init_blob() then we cannot later call foo.get_blob(),
because foo moves into the first call
and cannot be used again.
As the ::zap::data::Builder<'a> type is in fact just a typedef for &'a mut [u8],
it should be extra clear here why exclusivity is important to maintain.
One thing we might do with these accessors is
initialize the Foo and return a reference to its interior,
as does this function:
fn init_and_return_slice<'a>(foo : foo::Builder<'a>) -> &'a mut [u8] {
foo.init_blob(100).slice_mut(5, 10)
}
But what if we want to call this function and
then afterwards call set_x()?
We might write something like this:
fn do_some_things_wrong<'a>(mut foo : foo::Builder<'a>) {
{
let slice = init_and_return_slice(foo);
slice[0] = 42;
}
foo.set_x(1.23);
}
but if we try to compile this function, we get the following typecheck error:
main.rs:19:9: 19:12 error: use of moved value: `foo`
main.rs:19 foo.set_x(1.23);
^~~
The same pass-by-move semantics that were essential to preventing
aliasing have now become a problem.
We would like to be
able to borrow foo for just the inner block,
and then reuse it for the final line.
If foo were a built-in mutable reference, such a reborrow
would take place automatically, and everything would just work.
Fortunately, we can make do with our custom mutable reference
if we use the following following function,
which is also included in the generated code:
mod foo {
...
impl <'a> Builder <'a> {
pub fn borrow<'b>(&'b mut self) -> Builder<'b> { ... }
}
...
}
Using this, we can write our function as follows, and it successfully typechecks.
fn do_some_things_right<'a>(mut foo : foo::Builder<'a>) {
{
let slice = init_and_return_slice(foo.borrow());
slice[0] = 42
}
foo.set_x(1.23);
}
So it appears that the main inconviences of using our custom mutable references
compared to built-in mutable references
is that we need to add some calls to .borrow() and maybe add some mut's to some bindings.
In fact, it seems to me that it would be possible for Rust to support
a built-in Reborrow trait that could eliminate even these
inconveniences.
Finally, in case you're wondering why we prefer by-move self over &mut self
in our generated accessor methods, suppose that we also define this type in our schema:
struct Bar {
oneFoo @0 : Foo;
}
Using by-move self allows us to return references deep in the interior of a Bar, like this:
fn init_field_and_return_slice<'a>(bar : bar::Builder<'a>) -> &'a mut [u8] {
bar.init_one_foo().init_blob(100).slice_mut(5, 10)
}
If init_blob() instead took a &mut self parameter, this function would fail to typecheck
because the foo::Builder returned by bar.init_one_foo() does not live long enough.
update (26 March 2018)
The borrow() method has been renamed
to reborrow().