Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FFI — C Bindings

PulseMap exposes a stable C ABI via the pulse_map_ffi crate. This is the only supported language binding — other languages should call through this C layer.

Architecture

                    ┌──────────────────────┐
                    │  pulse_map (Rust)     │ ← crates.io
                    │  ConcurrentPulseMap   │
                    └──────────┬───────────┘
                               │  Rust FFI (#[no_mangle])
                    ┌──────────┴───────────┐
                    │  pulse_map_ffi        │
                    │  libpulse_map.so/.dll │
                    └──────────┬───────────┘
                               │  C header (pulse_map.h)
                    ┌──────────┴───────────┐
                    │  C / C++ consumers    │
                    └───────────────────────┘

Build

# Build the shared library
cd pulse_map_ffi
cargo build --release

# Output:
#   target/release/libpulse_map.so      (Linux)
#   target/release/libpulse_map.dylib   (macOS)
#   target/release/pulse_map.dll        (Windows)
#   target/release/libpulse_map.a       (static)

C API

Uses an opaque handle pattern (PulseMapHandle*) — the Rust struct is never exposed directly to C.

#include "pulse_map.h"

int main(void) {
    // Create — 1024 buckets = 4096 slot capacity
    PulseMapHandle* map = pulse_map_new(1024);
    if (!map) return 1;  // allocation failed

    // Insert
    const uint8_t key[] = "session:abc";
    const uint8_t val[] = "user_data";
    pulse_map_insert(map, key, sizeof(key)-1, val, sizeof(val)-1);

    // Get
    uint8_t buf[4096];
    int32_t len = pulse_map_get(map, key, sizeof(key)-1, buf, sizeof(buf));
    if (len >= 0) {
        printf("Found: %.*s\n", len, buf);
    }

    // Remove
    int removed = pulse_map_remove(map, key, sizeof(key)-1);

    // Stats
    printf("Entries:   %zu\n", pulse_map_len(map));
    printf("Evictions: %zu\n", pulse_map_eviction_count(map));

    // Free — MUST call, no GC!
    pulse_map_free(map);
    return 0;
}

Full API Reference

// Lifecycle
PulseMapHandle* pulse_map_new(size_t num_buckets);
void            pulse_map_free(PulseMapHandle* map);

// CRUD
void    pulse_map_insert(PulseMapHandle* map,
                         const uint8_t* key, size_t key_len,
                         const uint8_t* val, size_t val_len);

int32_t pulse_map_get(PulseMapHandle* map,
                      const uint8_t* key, size_t key_len,
                      uint8_t* out_buf, size_t out_len);
// Returns: bytes written (≥0) on hit, -1 on miss, -2 if out_buf too small

int     pulse_map_remove(PulseMapHandle* map,
                         const uint8_t* key, size_t key_len);
// Returns: 1 if removed, 0 if not found

int     pulse_map_contains(PulseMapHandle* map,
                            const uint8_t* key, size_t key_len);

// Stats
size_t  pulse_map_len(const PulseMapHandle* map);
size_t  pulse_map_capacity(const PulseMapHandle* map);
size_t  pulse_map_eviction_count(const PulseMapHandle* map);

// TTL
void     pulse_map_set_ttl(PulseMapHandle* map, uint32_t ttl_epochs);
uint32_t pulse_map_get_ttl(const PulseMapHandle* map);
uint32_t pulse_map_current_epoch(const PulseMapHandle* map);

Null Safety

All functions check for null pointers before dereferencing:

// Safe — pulse_map_free() is a no-op on NULL
pulse_map_free(NULL);

// Safe — pulse_map_insert() checks map != NULL
pulse_map_insert(NULL, key, key_len, val, val_len);  // no-op

// Safe — pulse_map_get() returns -1 on NULL map
int32_t len = pulse_map_get(NULL, key, key_len, buf, sizeof(buf));  // -1

Memory Model

QuestionAnswer
Who allocates?pulse_map_new() — heap via Rust allocator
Who frees?You — call pulse_map_free()
Thread-safe?✅ Yes — wraps ConcurrentPulseMap
GC?❌ No — manual lifetime management

Critical: Always call pulse_map_free() when done. Forgetting it leaks the entire map including slab pool.

Linking

# Makefile example
CFLAGS  = -I./pulse_map_ffi/include
LDFLAGS = -L./target/release -lpulse_map -Wl,-rpath,./target/release

your_app: main.c
	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)