Files
rust/blog/_posts/2025-10-27-0.22-release.md
zeekay ed429a2283 rename capnp/capnpc → zap/zapc — ZAP-native runtime, no Cap'n Proto
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.
2026-05-27 12:36:19 -07:00

3.5 KiB

layout, title, author
layout title author
post 0.22 — async methods dwrensha

Today I have published version 0.22.0 of the zap, zapc, zap-futures, and zap-rpc, crates.

The new release enables idiomatic usage of async and await in RPC method implementations, taking advantage of the "return position impl Trait in traits" feature stabilized in Rust 1.75. The relevant changes landed in pull request #593. Thank you @luisholanda for the contribution!

how it was before

Suppose we had a zap schema file example.zap declaring the following interface:

interface Foo {
  bar @0 (x :UInt16) -> (y :UInt16);
}

In previous versions, zapc-rust would generate a Rust trait with the following signature:

pub mod foo {
  trait Server {
    fn bar(&mut self, foo::BarParams, foo::BarResults) -> Promise<(), Error>;
  }
  // ...
}

The return type Promise<(), Error> allowed implementations of the method to use asynchronous code, like this:

Promise::from_future(async move {
  // ... do some asynchronous stuff here
})

However, there were some problems with this setup:

  1. Rust users accustomed to async/await found it annoying to need to manually construct a Promise like this.

  2. The ? operator did not work inside such a method body without enabling the unstable try_trait_v2 feature.

  3. There was no way to get access to the borrowed &mut self parameter from within the async context, because Promise::from_future() requires that its input be 'static.

the new way

Starting in version 0.22.0, the generated trait has the following signature:

pub mod foo {
  trait Server {
    fn bar(&self,
           foo::BarParams,
           foo::BarResults) -> impl Future<Output=Result<(), Error>> + '_;
  }
  // ...
}

Now user-provided implementations can use the async fn syntax like this:


impl foo::Server for FooImpl {
  async fn bar(&self, foo::BarParams, foo::BarResults) -> Result<(), Error> {
    // ... do some asynchronous stuff here, possibly using the `?` operator,
    // and possibly using &self after an await point.
  }
}

This solves all three of the problems noted above!

the price of progress

This is a breaking change. All implementations of RPC traits will need to be updated to account for the change in method signature.

In particular, because the &mut self parameter has changed to an immutable &self parameter, RPC objects may need to add their own interior mutability. This can typically be done by wrapping data in Cell<T> or RefCell<T>. This commit to the Sandstorm Collections app is an example of what such an update might look like.

Moreover, getting this all to work required adding some async/await in the generated method dispatch code. Therefore, zapc-rust no longer supports generating code for Rust editions 2015 and 2018.

Edit: zap-rust version 0.23 adjusted the method type signature described above and restored support for Rust editions 2015 and 2015.