Files
rust/blog/_posts/2020-06-06-no-std-support.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.2 KiB

layout, title, author
layout title author
post no_std support dwrensha

Over the past few years, many people have expressed interest in using zap-rust in no_std environments -- that is, without pulling in the Rust standard library. Today I'm happy to announce that the latest release, version 0.13.0, supports that.

To use a no_std zap-rust, update your Cargo.toml to the new zap version and disable default features, like this:

[zap.dependencies]
version = "0.13"
default-features = false

This turns off the new "std" feature flag in the zap crate. In turn, that feature controls a crate-level no_std attribute and gates the parts of the crate that depend on the standard library.

Example

To see no_std zap-rust in action, check out this new example that passes data to a WebAssembly function through a ZAP message. I observed the size of this example's generated wasm code to shrink from 1.6MB down to 660KB when I added #![no_std].

I/O traits

The biggest challenge in getting zap-rust to work with no_std was dealing with input/output traits. In previous releases, zap-rust defined its main serialization functions in terms of std::io::Read and std::io::Write. That would be a problem in a no_std context, because those traits are stuck in std.

The solution I settled on was to define custom zap::io::Read and zap::io::Write traits, and then to define the zap serialization functions in terms of those.

Blanket impls like the following then allow existing call sites to continue to work without being altered:

#[cfg(feature="std")]
mod std_impls {
  impl <R: std::io::Read> crate::io::Read for R {
    ...
  }
  impl <W: std::io::Write> crate::io::Write for W {
    ...
  }
}

Why now?

Two recent Rust developments paved the way for today's release:

  1. The stabilization of the alloc crate means that collections like Vec are now usable with no_std. (zap-rust strives to minimize allocations, but still relies on the global allocator for some things like messages with a dynamic number of segments.)
  2. no_std support for async/await means that we can use async blocks wherever we want. Previously, we would have needed to define some custom Future implementations to avoid putting an async block in the zap crate.

Thanks

Many people contributed useful ideas in the discussion that led up to the 0.13 release. I am especially grateful to nicholastmosher and bbqsrc for submitting diffs that explored the design space.