> ## 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

> C ABI bridge between Rust core and Go runtime

# 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:

1. **Schema parsing** - `.cham` → JSON AST
2. **Schema validation** - Type checking with structured errors
3. **SQL generation** - Queries and migrations
4. **Memory management** - Safe allocation/deallocation across boundaries
5. **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:**

1. Go calls C function via `cgo`
2. C function marshals to Rust (UTF-8 strings)
3. Rust processes and returns JSON (serialized via `serde_json`)
4. C function returns pointer to Go
5. Go deserializes JSON and frees Rust memory

## C ABI Functions

Location: `chameleon-core/src/ffi/mod.rs`

### Parse Schema

```rust theme={null}
#[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:**

```c theme={null}
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

```rust theme={null}
#[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):**

```json theme={null}
{
  "valid": true,
  "errors": []
}
```

**Result JSON (failure):**

```json theme={null}
{
  "valid": false,
  "errors": [
    {
      "kind": "ValidationError",
      "message": "Entity 'User' has no primary key",
      "line": null,
      "column": null,
      "snippet": null,
      "suggestion": null
    }
  ]
}
```

### Generate SQL

```rust theme={null}
#[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

```rust theme={null}
#[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

```rust theme={null}
#[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

```rust theme={null}
#[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

```rust theme={null}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChameleonResult {
    Ok = 0,
    ParseError = 1,
    ValidationError = 2,
    InternalError = 3,
}
```

**Usage in Go:**

```go theme={null}
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

```go theme={null}
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

```go theme={null}
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:**

1. `C.CString(input)` - Allocate C string (must free with `C.free`)
2. `C.chameleon_parse_schema()` - Call Rust (returns Rust-allocated string)
3. `C.GoString(cResult)` - Copy to Go string
4. `C.chameleon_free_string(cResult)` - Free Rust allocation

### ValidateSchemaRaw

```go theme={null}
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

```go theme={null}
func Version() string {
    cVersion := C.chameleon_version()
    return C.GoString(cVersion)
}
```

**Note:** No need to free - static string in Rust

## Building libchameleon.so

### Prerequisites

```bash theme={null}
# 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

```bash theme={null}
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:**

```bash theme={null}
sudo cp target/release/libchameleon.so /usr/local/lib/
sudo ldconfig
```

**macOS:**

```bash theme={null}
sudo cp target/release/libchameleon.dylib /usr/local/lib/
sudo update_dyld_shared_cache  # Optional, speeds up loading
```

### Verify Installation

```bash theme={null}
# 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`

```toml theme={null}
[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`)

## C Header Generation

Location: `chameleon-core/build.rs:22`

```rust theme={null}
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`):

```c theme={null}
#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

```bash theme={null}
# 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

```bash theme={null}
# 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

```dockerfile theme={null}
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:**

```go theme={null}
result := C.chameleon_parse_schema(input, &err)
C.chameleon_free_string(result)
C.chameleon_free_string(result)  // CRASH!
```

**❌ Memory leak:**

```go theme={null}
result := C.chameleon_parse_schema(input, &err)
return C.GoString(result)  // Leak! Never freed
```

**✅ Correct:**

```go theme={null}
result := C.chameleon_parse_schema(input, &err)
defer C.chameleon_free_string(result)
return C.GoString(result)
```

## Performance Characteristics

| 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

```rust theme={null}
#[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)

```go theme={null}
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:**

```bash theme={null}
# 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:**

```bash theme={null}
# 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:**

```bash theme={null}
# 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

* [Parser and AST](/api/parser) - Rust parser implementation
* [Type Checker](/api/type-checker) - Validation before FFI export
* [cbindgen Documentation](https://github.com/eqrion/cbindgen) - C header generation
* [cgo Documentation](https://pkg.go.dev/cmd/cgo) - Go C interop
