Skip to content

feat(bindings-cpp-ffi): add Rust FFI crate for WASM modules#4773

Open
euxaristia wants to merge 2 commits intoclockworklabs:masterfrom
euxaristia:feat/bindings-cpp-ffi-rust-replacement
Open

feat(bindings-cpp-ffi): add Rust FFI crate for WASM modules#4773
euxaristia wants to merge 2 commits intoclockworklabs:masterfrom
euxaristia:feat/bindings-cpp-ffi-rust-replacement

Conversation

@euxaristia
Copy link
Copy Markdown

@euxaristia euxaristia commented Apr 10, 2026

Summary

Add crates/bindings-cpp-ffi, a new Rust crate providing type registration and FFI
dispatch for SpacetimeDB WASM modules. It re-implements the logic from
crates/bindings-cpp (specifically module_type_registration.cpp,
module_exports.cpp, and Module.cpp) in Rust.

Motivation: The existing C++ bindings have no internal unit tests — all
existing tests are integration-level (WASM compile → publish to server). This
makes it impossible to test type registration edge cases, error paths, and FFI
dispatch in isolation, and means iteration requires a full Emscripten +
SpacetimeDB server cycle. A Rust implementation gives us:

  • Fast, offline, deterministic unit tests for the entire type registration and
    dispatch pipeline
  • Shared types with spacetimedb-lib, spacetimedb-sats, and
    spacetimedb-primitives (no hand-rolled mirrors of Rust types in C++)
  • A maintenance path fully within the Rust toolchain

Replacement intent: Yes. If the team is happy with this implementation, a
follow-up PR would remove crates/bindings-cpp and wire the SDK macros to this
crate instead. This PR only adds the crate — nothing is removed.

What's included

Type Registration (module_type_registration.rs)

  • Full SATS type registration pipeline: primitives → arrays → special types →
    options → results → ScheduleAt → user-defined structs/enums
  • Circular reference detection with error module generation
  • Error type generation for all failure modes (circular refs, multiple PKs,
    constraint errors, type registration errors) — matching the C++
    __preinit__99_validate_types behavior
  • BSATN serialization of RawModuleDefV10
  • 71 unit tests (100% line coverage)

FFI Dispatch (ffi.rs)

  • __describe_module__ — serializes module definition to host
  • __call_reducer__ — validates ID, reads BSATN args, dispatches to registered
    handler, writes errors to sink
  • __call_view__ / __call_view_anon__ — reconstructs sender identity, reads
    args, dispatches, prepends ViewResultHeader
  • __call_procedure__ — full dispatch with sender/connection reconstruction
  • __preinit__01_clear_global_state / __preinit__99_validate_types — WASM
    preinit exports
  • bytes_sink_write / bytes_source_read / bytes_source_remaining_length
    FFI I/O with partial read handling
  • Identity & ConnectionId little-endian reconstruction matching the C++ exactly
  • 25 unit tests with host FFI stubs

Public API

  • register_reducer, register_view, register_view_anon,
    register_procedure — handler registration returning dispatch indices
  • register_type — type registration with the typespace
  • has_registration_error / registration_error — error introspection

Size comparison

C++ Original Rust Implementation Delta
Production code 1,414 lines 1,139 lines -19%
Unit tests 0 96 +∞
Integration tests ~60 modules (type isolation)
Clippy N/A Clean
Line coverage Unknown 100%

Existing C++ test infrastructure

The C++ bindings do have integration-level tests:

  • Type isolation test suite (tests/type-isolation-test/): 60 test
    modules that validate WASM compilation and publish to a running server
  • Client comparison suite (tests/client-comparison/): validates that
    Rust and C++ bindings produce equivalent generated SDK clients and schemas
  • Smoketest (crates/smoketests/): test_quickstart_cpp exercises the
    full quickstart flow with a C++ server module

However, there are no unit tests covering the type registration or FFI
dispatch internals. The CTest target (test_bsatn) in CMakeLists.txt
references source files that don't exist on disk.

Verification

cargo test -p spacetimedb-bindings-cpp-ffi     # 96 passed
cargo clippy -p spacetimedb-bindings-cpp-ffi   # clean (-D warnings)
cargo fmt -p spacetimedb-bindings-cpp-ffi      # clean

Notes

  • Added to the workspace members list; builds cleanly alongside existing
    C++ bindings
  • No C++ code is removed in this PR — that would be a follow-up

@euxaristia euxaristia changed the title feat(bindings-cpp-ffi): add Rust replacement for C++ bindings feat(bindings-cpp-ffi): add Rust FFI crate for WASM modules Apr 10, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb1e667316

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/bindings-cpp-ffi/src/ffi.rs Outdated
Comment thread crates/bindings-cpp-ffi/src/ffi.rs Outdated
@euxaristia euxaristia force-pushed the feat/bindings-cpp-ffi-rust-replacement branch from bb1e667 to 22e9c5d Compare April 10, 2026 04:58
Rewrite the core C++ type registration and FFI dispatch layer from
crates/bindings-cpp in Rust. The new crate provides:

- ModuleTypeRegistration: full SATS type registration with circular
  reference detection, error module generation, and BSATN serialization
- FFI dispatch: __describe_module__, __call_reducer__, __call_view__,
  __call_view_anon__, __call_procedure__ with proper Identity/ConnectionId
  reconstruction and BytesSource/BytesSink I/O
- Preinit functions: __preinit__01_clear_global_state and
  __preinit__99_validate_types matching the C++ originals

Replaces ~1,414 lines of C++ with ~1,139 lines of Rust (19% reduction)
plus 96 unit tests (100% coverage). Clippy-clean and fmt-clean.
@euxaristia euxaristia force-pushed the feat/bindings-cpp-ffi-rust-replacement branch from 22e9c5d to c07421e Compare April 10, 2026 05:17
@euxaristia
Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@bfops
Copy link
Copy Markdown
Collaborator

bfops commented Apr 27, 2026

Hi @euxaristia, what's the goal of this PR? Why is this version preferable to what's there? Is it intended as a replacement?

- Store (data, cursor) instead of raw Vec<u8>
- bytes_source_read: copy from cursor position, advance cursor
- bytes_source_read: return 0 while data remains, -1 when exhausted
- bytes_source_read: always write *len (0 when exhausted) to avoid
  reading uninitialized buffer capacity as valid bytes
- bytes_source_remaining_length: report remaining bytes, not total
@euxaristia
Copy link
Copy Markdown
Author

@bfops rewrites the C++ binding layer in Rust. The C++ side has integration tests (type-isolation, client-comparison) but zero unit tests on the internal logic, so edge cases/errors aren't covered. This adds 96 unit tests at 100% coverage, uses shared Rust types directly, and keeps everything in one toolchain.

Yes, intended as a replacement, but this PR only adds the crate — nothing removed. Follow-up would swap it in if the team's on board.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants