Documentation Index
Fetch the complete documentation index at: https://docs.chameleondb.dev/llms.txt
Use this file to discover all available pages before exploring further.
FFI Interface
The FFI (Foreign Function Interface) layer enables communication between the Rust core (chameleon-core) and Go runtime (chameleon) via a stable C ABI. This boundary handles schema parsing, validation, SQL generation, and memory management across language boundaries.
Overview
The FFI bridge provides:
- Schema parsing -
.cham → JSON AST
- Schema validation - Type checking with structured errors
- SQL generation - Queries and migrations
- Memory management - Safe allocation/deallocation across boundaries
- Version checking - Ensure Rust/Go compatibility
Architecture
┌─────────────────────────────────────────────────┐
│ Go Application │
│ (chameleon/pkg/engine) │
└────────────────┬────────────────────────────────┘
│
│ Go FFI bindings (cgo)
│
┌────────────────▼────────────────────────────────┐
│ C ABI Boundary │
│ - chameleon_parse_schema() │
│ - chameleon_validate_schema() │
│ - chameleon_generate_sql() │
│ - chameleon_generate_migration() │
│ - chameleon_free_string() │
└────────────────┬────────────────────────────────┘
│
│ unsafe extern "C"
│
┌────────────────▼────────────────────────────────┐
│ Rust Core Library │
│ (chameleon-core/src/ffi/mod.rs) │
│ - Parser (LALRPOP) │
│ - Type Checker │
│ - SQL Generator │
└─────────────────────────────────────────────────┘
Data flow:
- Go calls C function via
cgo
- C function marshals to Rust (UTF-8 strings)
- Rust processes and returns JSON (serialized via
serde_json)
- C function returns pointer to Go
- Go deserializes JSON and frees Rust memory
C ABI Functions
Location: chameleon-core/src/ffi/mod.rs
Parse Schema
#[no_mangle]
pub unsafe extern "C" fn chameleon_parse_schema(
input: *const c_char,
error_out: *mut *mut c_char,
) -> *mut c_char
Parameters:
input - Null-terminated .cham source code
error_out - Output pointer for error JSON (if any)
Returns:
- JSON AST on success (caller must free with
chameleon_free_string)
NULL on failure (error_out contains error JSON)
Example:
char* error = NULL;
char* schema_json = chameleon_parse_schema(
"entity User { id: uuid primary, }",
&error
);
if (schema_json == NULL) {
printf("Parse error: %s\n", error);
chameleon_free_string(error);
} else {
printf("Schema: %s\n", schema_json);
chameleon_free_string(schema_json);
}
Validate Schema
#[no_mangle]
pub unsafe extern "C" fn chameleon_validate_schema(
input: *const c_char,
error_out: *mut *mut c_char,
) -> ChameleonResult
Parameters:
input - Null-terminated .cham source code
error_out - Output pointer for validation result JSON
Returns:
ChameleonResult::Ok - Schema is valid
ChameleonResult::ParseError - Syntax error
ChameleonResult::ValidationError - Type check failed
ChameleonResult::InternalError - Unexpected error
Result JSON (success):
{
"valid": true,
"errors": []
}
Result JSON (failure):
{
"valid": false,
"errors": [
{
"kind": "ValidationError",
"message": "Entity 'User' has no primary key",
"line": null,
"column": null,
"snippet": null,
"suggestion": null
}
]
}
Generate SQL
#[no_mangle]
pub unsafe extern "C" fn chameleon_generate_sql(
query_json: *const c_char,
schema_json: *const c_char,
error_out: *mut *mut c_char,
) -> ChameleonResult
Parameters:
query_json - Query specification (JSON)
schema_json - Schema AST (JSON)
error_out - Output pointer for generated SQL (JSON)
Returns:
ChameleonResult::Ok - SQL generated (error_out contains result)
ChameleonResult::ValidationError - Invalid query
ChameleonResult::InternalError - Unexpected error
Generate Migration
#[no_mangle]
pub unsafe extern "C" fn chameleon_generate_migration(
schema_json: *const c_char,
error_out: *mut *mut c_char,
) -> ChameleonResult
Parameters:
schema_json - Schema AST (JSON)
error_out - Output pointer for migration SQL
Returns:
ChameleonResult::Ok - Migration SQL in error_out
ChameleonResult::ValidationError - Invalid schema
ChameleonResult::InternalError - Unexpected error
Free String
#[no_mangle]
pub unsafe extern "C" fn chameleon_free_string(s: *mut c_char)
Parameters:
s - String allocated by Rust (from any FFI function)
Safety:
- Safe to call with
NULL (no-op)
- Must be called for every non-NULL pointer returned by Rust
- Never call twice on the same pointer (double-free)
Version
#[no_mangle]
pub extern "C" fn chameleon_version() -> *const c_char
Returns:
- Static string with Rust core version (e.g., “0.1.0-beta”)
- Never needs to be freed (static lifetime)
Result Codes
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChameleonResult {
Ok = 0,
ParseError = 1,
ValidationError = 2,
InternalError = 3,
}
Usage in Go:
const (
ResultOk = C.CHAMELEON_OK
ResultParseError = C.CHAMELEON_PARSE_ERROR
ResultValidationError = C.CHAMELEON_VALIDATION_ERROR
ResultInternalError = C.CHAMELEON_INTERNAL_ERROR
)
Go Bindings
Location: chameleon/internal/ffi/bindings.go
CGO Setup
package ffi
/*
#cgo linux LDFLAGS: -L/usr/local/lib -Wl,-rpath,/usr/local/lib -lchameleon
#cgo darwin LDFLAGS: -L/usr/local/lib -L/opt/homebrew/lib -Wl,-rpath,/usr/local/lib -Wl,-rpath,/opt/homebrew/lib -lchameleon
#include <stdlib.h>
typedef enum {
CHAMELEON_OK = 0,
CHAMELEON_PARSE_ERROR = 1,
CHAMELEON_VALIDATION_ERROR = 2,
CHAMELEON_INTERNAL_ERROR = 3,
} ChameleonResult;
char* chameleon_parse_schema(const char* input, char** error_out);
ChameleonResult chameleon_validate_schema(const char* schema_json, char** error_out);
void chameleon_free_string(char* s);
const char* chameleon_version(void);
extern int chameleon_generate_sql(const char* query_json, const char* schema_json, char** error_out);
extern int chameleon_generate_migration(const char* schema_json, char** error_out);
*/
import "C"
LDFLAGS explanation:
-L/usr/local/lib - Library search path
-Wl,-rpath,/usr/local/lib - Runtime library path (embeds path in binary)
-lchameleon - Link against libchameleon.so (Linux) or libchameleon.dylib (macOS)
ParseSchema
func ParseSchema(input string) (string, error) {
cInput := C.CString(input)
defer C.free(unsafe.Pointer(cInput))
var cError *C.char
defer func() {
if cError != nil {
C.chameleon_free_string(cError)
}
}()
cResult := C.chameleon_parse_schema(cInput, &cError)
if cResult == nil {
if cError != nil {
return "", errors.New(C.GoString(cError))
}
return "", errors.New("unknown parse error")
}
defer C.chameleon_free_string(cResult)
return C.GoString(cResult), nil
}
Memory management:
C.CString(input) - Allocate C string (must free with C.free)
C.chameleon_parse_schema() - Call Rust (returns Rust-allocated string)
C.GoString(cResult) - Copy to Go string
C.chameleon_free_string(cResult) - Free Rust allocation
ValidateSchemaRaw
func ValidateSchemaRaw(schemaInput string) (string, error) {
cInput := C.CString(schemaInput)
defer C.free(unsafe.Pointer(cInput))
var cError *C.char
defer func() {
if cError != nil {
C.chameleon_free_string(cError)
}
}()
result := C.chameleon_validate_schema(cInput, &cError)
if cError != nil {
errMsg := C.GoString(cError)
if result == ResultOk {
return errMsg, nil // Success JSON
}
return errMsg, errors.New("validation failed")
}
return "", errors.New("unknown error")
}
Version
func Version() string {
cVersion := C.chameleon_version()
return C.GoString(cVersion)
}
Note: No need to free - static string in Rust
Building libchameleon.so
Prerequisites
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install build dependencies
sudo apt-get install build-essential # Linux
brew install llvm # macOS
Build Steps
cd chameleon-core
# Build shared library
cargo build --release
# Output: target/release/libchameleon.so (Linux)
# target/release/libchameleon.dylib (macOS)
# target/release/chameleon.dll (Windows)
Install System-Wide
Linux:
sudo cp target/release/libchameleon.so /usr/local/lib/
sudo ldconfig
macOS:
sudo cp target/release/libchameleon.dylib /usr/local/lib/
sudo update_dyld_shared_cache # Optional, speeds up loading
Verify Installation
# Check library dependencies
ldd /usr/local/lib/libchameleon.so # Linux
otool -L /usr/local/lib/libchameleon.dylib # macOS
# Test from Go
cd ../chameleon
go test ./internal/ffi/...
Cargo Configuration
Location: chameleon-core/Cargo.toml
[package]
name = "chameleon"
version = "0.1.0-beta"
edition = "2021"
[lib]
name = "chameleon"
crate-type = ["rlib", "staticlib", "cdylib"]
[dependencies]
lalrpop-util = { version = "0.20", features = ["lexer"] }
regex = "1.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
lazy_static = "1.4"
[build-dependencies]
lalrpop = "0.20"
cbindgen = "0.27"
Crate types:
rlib - Rust static library (for Rust consumers)
staticlib - C-compatible static library (.a)
cdylib - C-compatible dynamic library (.so, .dylib, .dll)
Location: chameleon-core/build.rs:22
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(cbindgen::Language::C)
.with_include_guard("CHAMELEON_H")
.generate()
.expect("Unable to generate bindings")
.write_to_file("include/chameleon.h");
Generated header (include/chameleon.h):
#ifndef CHAMELEON_H
#define CHAMELEON_H
#include <stdint.h>
typedef enum {
ChameleonResult_Ok = 0,
ChameleonResult_ParseError = 1,
ChameleonResult_ValidationError = 2,
ChameleonResult_InternalError = 3,
} ChameleonResult;
char *chameleon_parse_schema(const char *input, char **error_out);
ChameleonResult chameleon_validate_schema(const char *input, char **error_out);
void chameleon_free_string(char *s);
const char *chameleon_version(void);
ChameleonResult chameleon_generate_sql(const char *query_json,
const char *schema_json,
char **error_out);
ChameleonResult chameleon_generate_migration(const char *schema_json,
char **error_out);
#endif /* CHAMELEON_H */
Linking from Go
Development Build
# Build Rust library
cd chameleon-core
cargo build --release
# Set library path for Go
export LD_LIBRARY_PATH=$PWD/target/release:$LD_LIBRARY_PATH # Linux
export DYLD_LIBRARY_PATH=$PWD/target/release:$DYLD_LIBRARY_PATH # macOS
# Build Go application
cd ../chameleon
go build ./cmd/chameleon
Production Build
# Install library system-wide
sudo cp chameleon-core/target/release/libchameleon.so /usr/local/lib/
sudo ldconfig
# Go will find library via rpath (set in cgo LDFLAGS)
go build ./cmd/chameleon
./chameleon --version
Docker Build
FROM rust:1.75 AS rust-builder
WORKDIR /build
COPY chameleon-core .
RUN cargo build --release
FROM golang:1.21 AS go-builder
WORKDIR /build
COPY --from=rust-builder /build/target/release/libchameleon.so /usr/local/lib/
RUN ldconfig
COPY chameleon .
RUN go build -o chameleon ./cmd/chameleon
FROM ubuntu:22.04
COPY --from=rust-builder /build/target/release/libchameleon.so /usr/local/lib/
COPY --from=go-builder /build/chameleon /usr/local/bin/
RUN ldconfig
ENTRYPOINT ["chameleon"]
Memory Management
Ownership Rules
| Allocator | Deallocator | Example |
|---|
Go (C.CString) | Go (C.free) | Input strings to Rust |
Rust (CString::into_raw) | Rust (chameleon_free_string) | Return values from Rust |
| Rust (static) | Never | chameleon_version() |
Common Pitfalls
❌ Double-free:
result := C.chameleon_parse_schema(input, &err)
C.chameleon_free_string(result)
C.chameleon_free_string(result) // CRASH!
❌ Memory leak:
result := C.chameleon_parse_schema(input, &err)
return C.GoString(result) // Leak! Never freed
✅ Correct:
result := C.chameleon_parse_schema(input, &err)
defer C.chameleon_free_string(result)
return C.GoString(result)
| Operation | Overhead | Notes |
|---|
| FFI call | ~100ns | Function call + argument marshaling |
| String copy (Go → C) | ~50ns/KB | C.CString() allocation |
| String copy (C → Go) | ~50ns/KB | C.GoString() allocation |
| JSON serialization | ~1μs/entity | serde_json::to_string() |
| JSON deserialization | ~2μs/entity | json.Unmarshal() in Go |
Total parse overhead:
- Small schema (5 entities): ~500μs
- Medium schema (20 entities): ~2ms
- Large schema (100 entities): ~10ms
Comparison to pure Go parser:
- FFI overhead: +30% (but Rust parser is 3x faster)
- Net result: 2x faster than pure Go
Testing
Location: chameleon-core/src/ffi/mod.rs:471
Rust Tests
#[test]
fn test_parse_schema_success() {
let input = CString::new("entity User { id: uuid primary, }").unwrap();
let mut error: *mut c_char = ptr::null_mut();
unsafe {
let result = chameleon_parse_schema(input.as_ptr(), &mut error);
assert!(!result.is_null());
assert!(error.is_null());
let json_str = CStr::from_ptr(result).to_str().unwrap();
assert!(json_str.contains("User"));
chameleon_free_string(result);
}
}
Go Tests
Location: chameleon/internal/ffi/bindings_test.go (hypothetical)
func TestParseSchema(t *testing.T) {
input := `entity User { id: uuid primary, }`
result, err := ParseSchema(input)
require.NoError(t, err)
assert.Contains(t, result, "User")
var schema map[string]interface{}
err = json.Unmarshal([]byte(result), &schema)
require.NoError(t, err)
}
func TestVersion(t *testing.T) {
version := Version()
assert.NotEmpty(t, version)
assert.Regexp(t, `^\d+\.\d+\.\d+`, version)
}
Troubleshooting
Library Not Found
Error:
error while loading shared libraries: libchameleon.so: cannot open shared object file
Solution:
# Add to LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# Or install system-wide
sudo cp libchameleon.so /usr/local/lib/
sudo ldconfig
Symbol Not Found
Error:
undefined reference to `chameleon_parse_schema'
Causes:
- Rust library not built with
cdylib crate type
- Function not marked
#[no_mangle]
- Header out of sync with implementation
Solution:
# Rebuild with correct crate type
cargo clean
cargo build --release
# Verify symbols
nm -D target/release/libchameleon.so | grep chameleon
Version Mismatch
Error:
FFI version mismatch: Go expects 0.1.0, Rust is 0.2.0
Solution:
# Check versions
go run ./cmd/chameleon version
cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "chameleon") | .version'
# Rebuild both
cd chameleon-core && cargo build --release
cd ../chameleon && go build ./cmd/chameleon
See Also