Expand description
C FFI bindings for cross-language integration.
This module provides a C-compatible API for using Net from other languages (Python, Node.js, Go, etc.).
§Safety
All public FFI functions in this module accept raw pointers from C code.
Each is declared pub unsafe extern "C" fn so the unsafety is
explicit at the type level; the module-wide contract callers
must uphold is:
- Pointers are valid and properly aligned
- Opaque handle pointers (
*mut T) were produced by this crate’s matching constructor (Box::into_rawinside the FFI surface). Foreign-allocated pointers, even if valid and aligned, will UB when consumed byBox::from_rawin the corresponding_free. - String pointers point to valid UTF-8 data
- Buffer sizes are accurate
- Handles are not used after
net_shutdown
The per-function # Safety rustdoc is intentionally suppressed
at the module level — every entry point shares the same contract
and the module doc-comment above (plus include/README.md) is
the source of truth. Adding individual # Safety blocks would
duplicate the same wording 200 times without adding signal.
§Thread Safety
All FFI functions are thread-safe. The event bus handle can be shared across threads.
§Tokio runtime restriction
Internal FFI ops (net_poll, net_flush, net_shutdown,
net_redex_*, net_mesh_new, the cortex FFI, the mesh FFI)
drive the bus’s tokio runtime via Runtime::block_on. That
function panics with “Cannot start a runtime from within a
runtime” if the calling thread is already inside a tokio
runtime context. The functions are extern "C", so a panic
unwinds across the FFI boundary into C / Go-cgo / Python /
NAPI — undefined behavior.
The common-case C / Go / Python caller has no Rust tokio runtime, so this is unreachable for them. The narrow path is:
- A Rust caller loads the cdylib and calls these
functions from inside its own
#[tokio::main](or any thread that has calledRuntime::enter()). - A non-Rust caller embeds a Rust library that runs its own tokio runtime and forwards calls into this cdylib on the same thread.
Both forms are unusual but reachable. Do not call any FFI
op from a thread that already holds a tokio runtime
context. If you must, spawn the FFI call on a fresh OS
thread that doesn’t carry a runtime guard, or wrap the call
with tokio::task::spawn_blocking(|| net_xxx(...)) to escape
the worker pool.
net_init (mod.rs:284-316) hardens against this for runtime
construction; the steady-state ops do not, since the cost
of a Handle::try_current() check on every poll would be
measurable for the common path that doesn’t hit the bug.
§catch_unwind + caller-held locks
Several FFI entries (net_blob_publish, net_blob_resolve,
net_*_wait_for_token) wrap their body in
std::panic::catch_unwind(AssertUnwindSafe(...)) so a panic
during the call returns a typed NET_ERR_BLOB_PANIC /
NET_ERR_PANIC code rather than unwinding across the FFI
boundary. That stops the substrate-side undefined behavior,
but it does NOT make the wrapped code transparently panic-safe
from the caller’s perspective.
If the caller invokes an FFI op while holding an OS-level
lock, a sync.Mutex (Go), threading.Lock (Python), or any
other mutex with poisoning semantics, and the FFI body panics,
the mutex is left in a poisoned state. Subsequent acquires
on the same mutex by the caller observe the poisoning and
either error (Rust parking_lot with poison_on_unwind) or
deadlock (Go’s sync.Mutex doesn’t poison; the caller has
observed a return value that may not reflect the state of
the FFI op).
Recommended caller pattern: do not hold a caller-side lock across an FFI call. Acquire the lock, prepare the inputs, release the lock, then call the FFI. Re-acquire if you need to update caller state with the result.
The hazard is documented per-binding in:
- Python:
bindings/python/README.md(caller-mutex notes) - Node:
bindings/node/README.md - Go:
bindings/go/net/redex.golifecycle docs - C:
include/net.h(every wait-family declaration)
§Memory Management
- Handles returned by
net_initmust be freed withnet_shutdown - String buffers passed to
net_pollare owned by the caller - Error codes are returned as integers (0 = success, negative = error)
§Example (C)
#include "net.h"
int main() {
// Initialize with default config
void* bus = net_init("{\"num_shards\": 4}");
if (!bus) return 1;
// Ingest an event
int result = net_ingest(bus, "{\"token\": \"hello\"}", 19);
if (result < 0) { /* handle error */ }
// Poll events
char buffer[65536];
result = net_poll(bus, "{\"limit\": 100}", buffer, sizeof(buffer));
// Shutdown
net_shutdown(bus);
return 0;
}Modules§
- aggregator
- C FFI for the
aggregator.registryRPC client + channel visibility setter. Stage 5 ofSDK_AGGREGATOR_SUBNET_PLAN.md. Rides thenetfeature alongsideffi::meshbecause every op needs aMeshNodeHandle. C FFI bindings for the aggregator-registry RPC client + fold-query client + channel-visibility setter (SDK_AGGREGATOR_SUBNET_PLAN.mdstages 5 + 4-fold-query). - blob
- C FFI for the Dataforts Phase 3 blob surface. Exposes the
BlobRef wire codec, the global adapter registry, and the
publish_blob/resolve_payloadhelpers for cgo / native consumers. C FFI for Dataforts Phase 3 blob storage. - handle_
guard - C FFI for CortEX / NetDb / RedexFile. Requires
netdb(for the unified facade) andredex-disk(for persistent storage paths onRedex/RedexFile). Go / cgo consumers target this surface. - mesh
- C FFI for the encrypted-UDP mesh transport + channels. Requires
the
netfeature (which brings in the crypto + transport). Go / cgo consumers target this surface alongsideffi::cortex. See theffi::cortexnote for whymissing_docsis suppressed here. C FFI bindings for the encrypted-UDP mesh transport. - predicate
- C FFI for stateless predicate evaluation (Phase 9c of
CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helpers — no handles, no state. Mirrors the SDK-layerevaluatePredicate/evaluate_predicatesurface every binding ships, exposed at the C ABI for raw consumers (C / C++ / Zig / Swift / etc.). C FFI for stateless predicate evaluation (Phase 9c ofCAPABILITY_SYSTEM_SDK_PLAN.md). - predicate_
debug - C FFI for predicate debug-session helpers (Phase 9d of
CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helpers — single-evalevaluate_with_trace, corpus-wideaggregate_debug_report, and host-sideredact_metadata_keys. Mirror what every other binding ships at the SDK layer; exposed at the C ABI for raw consumers. C FFI for predicate debug-session helpers (Phase 9d ofCAPABILITY_SYSTEM_SDK_PLAN.md). - schema
- C FFI for stateless capability-set validation (Phase 9a of
CAPABILITY_SYSTEM_SDK_PLAN.md). Pure helper —caps_jsonin,report_jsonout. Mirrors the SDK-layervalidate_capabilitiessurface, exposed at the C ABI for raw consumers. C FFI for stateless capability-set validation (Phase 9a ofCAPABILITY_SYSTEM_SDK_PLAN.md).
Structs§
- NetEvent
- A single stored event for C consumers.
- NetHandle
- Opaque handle to an event bus instance.
- NetPoll
Result - Poll result for C consumers.
- NetReceipt
- Ingestion receipt for C consumers.
- NetStats
- Stats for C consumers.
Enums§
- NetError
- Error codes returned by FFI functions.
Functions§
- net_
flush ⚠ - Flush all pending batches to the adapter.
- net_
free_ ⚠poll_ result - Free the internal allocations of a poll result returned by
net_poll_ex. - net_
free_ ⚠string - Free a string returned by Net functions.
- net_
generate_ ⚠keypair - Generate a new Net keypair.
- net_
ingest ⚠ - Ingest a single event.
- net_
ingest_ ⚠batch - Ingest multiple events.
- net_
ingest_ ⚠raw - Ingest a raw JSON string (fastest path).
- net_
ingest_ ⚠raw_ batch - Ingest multiple raw JSON strings (fastest batch path).
- net_
ingest_ ⚠raw_ ex - Ingest raw JSON with structured receipt.
- net_
init ⚠ - Initialize a new event bus.
- net_
num_ ⚠shards - Get the number of shards.
- net_
poll ⚠ - Poll events from the bus.
- net_
poll_ ⚠ex - Poll events with structured result (no JSON overhead).
- net_
shutdown ⚠ - Shut down the event bus and free resources.
- net_
stats ⚠ - Get event bus statistics.
- net_
stats_ ⚠ex - Get stats without JSON serialization.
- net_
version ⚠ - Get the library version.