Please read the top-level CONTRIBUTING.md first for general contribution guidelines, pull request process, and commit requirements.
This document covers C++-specific development setup and workflow.
- Git
- A C++17 compiler (GCC, Clang, or MSVC)
- CMake 3.16 or higher
- Rust toolchain (cargo) — Install Rust, required to build the C FFI library from local source (the default)
-
Clone the repository:
git clone https://github.com/databricks/zerobus-sdk.git cd zerobus-sdk/cpp -
Build the project:
make build
This will:
- Build the Rust C FFI static library (
cargo build --releasein../rust/ffi) - Configure and build the C++ SDK and its tests
- Build the Rust C FFI static library (
-
Run tests:
make test
To link a prebuilt/vendored FFI library instead of building it from source,
configure CMake with -DZEROBUS_FFI_LIBRARY=<path-to-.a> and
-DZEROBUS_FFI_HEADER_DIR=<dir containing zerobus.h>.
Code style is enforced by clang-format (Google style; see .clang-format).
Format your code before committing:
make fmtCheck formatting without modifying files:
make fmt-checkWithout clang-tidy available everywhere, lint combines a formatting check
with the compiler's own -Wall -Wextra warnings (enabled on the library
target):
make lintRun the test suite to ensure your changes don't break existing functionality:
make testTests are dependency-free: each is a plain executable registered with CTest, so the build is hermetic (no vendored framework, no package manager, no network). To catch the memory/lifetime bugs an FFI wrapper is prone to (use-after-free, double-free), build and run the suite under a sanitizer:
make test SANITIZE=address # or thread, undefinedAll pull requests must pass CI checks:
- fmt: Runs the formatting check (
clang-format) - lint: Runs the formatting check plus a warnings-as-errors build
- test: Builds and runs the CTest suite
You can view CI results in the GitHub Actions tab of the pull request.
Available make targets:
make build— Configure (CMake) and build the SDK, tests, and examplesmake build-ffi— Build only the underlying Rust C FFI librarymake test— Build and run the test suite (ctest)make examples— Build the example binariesmake fmt— Format all C++ sources with clang-formatmake fmt-check— Check formatting without modifying filesmake lint— Run lint checks (formatting + compiler warnings)make check— Runfmt-checkandlintmake clean— Remove the build directorymake help— Show available targets
Add SANITIZE=address (or thread/undefined) to build and test under a
sanitizer, e.g. make test SANITIZE=address.
The public headers under include/zerobus/ never include the generated C header
(../rust/ffi/zerobus.h); they hold opaque, forward-declared FFI handles. Only
src/ includes zerobus.h, keeping the C FFI an implementation detail.
When making changes that touch the FFI boundary:
- Update the Rust code in
../rust/ffi/src/(and regeneratezerobus.hif signatures change) - Update the C++ wrapper in
src/and, if the public API changes, the headers underinclude/zerobus/ - Rebuild with
make build - Test the changes with
make test(andmake test SANITIZE=addressfor lifetime-sensitive changes)
- Rust owns the memory for opaque handles; the wrapper holds a raw pointer and frees it via the matching FFI function in the destructor. Wrapper objects are move-only so each handle is freed exactly once.
- Route every
CResultthrough the helper indetail/ffi_util.hpp, which converts failure into aZerobusExceptionand always freesCResult.error_messageviazerobus_free_error_message. - Free returned arrays and buffers with their matching FFI free function after copying the payload out.
- Never let a C++ exception cross the FFI boundary — exceptions from
HeadersProvider::get_headers()are caught and surfaced as a headers-provider error.
See CLAUDE.md for the full memory-ownership, threading, and
breaking-change contract.