hedl-ffi
C ABI bindings for HEDL—use the HEDL ecosystem from C, C++, Python (ctypes/cffi), Ruby (FFI), Go (cgo), and any language with C interop.
Many production systems use C/C++. Legacy code can't be rewritten in Rust overnight. Python, Ruby, Go need access to HEDL without rewriting the parser. Language bridges shouldn't sacrifice performance or correctness. Memory safety bugs in FFI code cause crashes and security vulnerabilities.
hedl-ffi provides production-grade C bindings to the complete HEDL implementation. Thread-safe error handling via thread-local storage. Memory safety through poison pointer detection and explicit freeing functions. Zero-copy callback patterns for large outputs. Comprehensive audit logging for production debugging.
What's Implemented
Complete C API with safety and observability:
- Parsing & Validation: Parse HEDL from strings, validate without parsing
- Format Conversion: Convert HEDL to/from JSON, YAML, XML, CSV, Parquet, Neo4j Cypher, TOON (optional features)
- Operations: Canonicalize, lint with diagnostics, gather statistics
- 18 Error Codes: Comprehensive error classification (OK, Parse, UTF-8, Alloc, etc.)
- Thread-Safe Error Handling: Thread-local storage for error messages
- Memory Safety: 4 explicit freeing functions (string, document, diagnostics, bytes)
- Poison Pointer Detection: Use-after-free prevention with poison values
- UTF-8 Validation: All string I/O checked for valid UTF-8
- Zero-Copy Callbacks: Streaming output via callback functions (no full buffering)
- Async Operations: Asynchronous operations with completion callbacks
- Audit Logging: Operation logging via
tracingcrate for production debugging
Installation
Building
# Outputs:
# Linux: target/release/libhedl.so
# macOS: target/release/libhedl.dylib
# Windows: target/release/hedl.dll
Header File
Core API
Parsing
const char* hedl_str = "%VERSION: 1.0\n---\nname: Test\nvalue: 42\n";
HedlDocument* doc = NULL;
int result = ;
if
// Use document...
;
Validation
const char* hedl_str = "%VERSION: 1.0\n---\nname: Test\nvalue: 42\n";
int result = ;
if
;
Format Conversion
HedlDocument* doc = NULL;
;
// HEDL → JSON
char* json = NULL;
if
// JSON → HEDL
const char* json_input = "{\"name\": \"Test\"}";
HedlDocument* doc2 = NULL;
if
// Other conversions
char* yaml = NULL;
if
char* xml = NULL;
if
char* csv = NULL;
if
;
Canonicalization
HedlDocument* doc = NULL;
;
char* canonical = NULL;
if
;
Linting
HedlDocument* doc = NULL;
;
HedlDiagnostics* diags = NULL;
if
;
Document Information
HedlDocument* doc = NULL;
;
// Get version
int major = 0, minor = 0;
if
// Get schema/alias/root counts
int schema_count = ;
int alias_count = ;
int root_count = ;
;
;
;
;
Zero-Copy Output Callback
For large outputs, use callback pattern to avoid full buffering:
// HedlOutputCallback signature:
// typedef void (*HedlOutputCallback)(const char *data, uintptr_t len, void *user_data);
void
HedlDocument* doc = NULL;
;
FILE* fp = ;
;
;
;
Available callback functions:
hedl_to_json_callback()- JSON output via callbackhedl_to_yaml_callback()- YAML output via callbackhedl_to_xml_callback()- XML output via callbackhedl_to_csv_callback()- CSV output via callbackhedl_to_neo4j_cypher_callback()- Neo4j Cypher output via callbackhedl_canonicalize_callback()- Canonical output via callback
Error Handling
Error Codes
All functions return an integer error code:
Thread-Local Error Messages
// Get last error message (thread-local)
const char* error = ;
// Thread-safe alias (same as hedl_get_last_error)
const char* error = ;
// Clear error state
;
Thread Safety: Error messages stored in thread-local storage. Each thread maintains independent error state. Safe to call from multiple threads without synchronization.
Memory Management
Freeing Functions
// Free string returned by hedl_to_* and hedl_canonicalize
void ;
// Free document returned by hedl_parse or hedl_from_*
void ;
// Free diagnostics returned by hedl_lint
void ;
// Free byte array returned by hedl_to_parquet
void ;
CRITICAL: Always call appropriate freeing function for each allocated object. Memory leaks occur if not freed. Only pass pointers that were allocated by HEDL functions.
Ownership and Lifetime
- Strings returned by
hedl_to_*functions are allocated by HEDL and must be freed withhedl_free_string() - Documents returned by
hedl_parse()andhedl_from_*()are allocated by HEDL and must be freed withhedl_free_document() - Diagnostics returned by
hedl_lint()are allocated by HEDL and must be freed withhedl_free_diagnostics() - Byte arrays returned by
hedl_to_parquet()are allocated by HEDL and must be freed withhedl_free_bytes(data, len) - Input parameters (HEDL/JSON/YAML strings) are NOT owned by the library; caller must manage their lifetime
Use-After-Free Protection
Poison pointer detection prevents most use-after-free bugs:
HedlDocument* doc = NULL;
;
;
// Using doc after free returns error code instead of crashing
char* json = NULL;
int result = ; // Returns HEDL_ERR_NULL_PTR
UTF-8 Validation
All string inputs and outputs are validated:
// Input validation
const char* invalid_utf8 = "\xFF\xFE invalid";
HedlDocument* doc = NULL;
int result = ;
// Returns HEDL_ERR_INVALID_UTF8
// Output validation
// All hedl_to_* functions guarantee valid UTF-8 output
Guarantees:
- No invalid UTF-8 propagated through API
- Error code returned on malformed input
- Safe for UTF-8 expecting consumers
Audit Logging
The FFI library integrates with the tracing crate for comprehensive operation logging. Logging captures:
- Function entry/exit with timing
- Sanitized parameters (pointer addresses masked)
- Success/failure outcomes with error details
- Performance metrics (call duration)
- Thread context
Configuring Logging:
Set the RUST_LOG environment variable:
# Log all INFO and above
# Log only FFI audit events at DEBUG level
# Log everything at DEBUG level
In Application Code (Rust):
use EnvFilter;
fmt
.with_env_filter
.with_target
.with_thread_ids
.with_line_number
.init;
// Now FFI calls are logged
Log Output Example:
2025-01-05T10:30:45.123Z INFO hedl_ffi::audit: FFI call started function="hedl_parse" thread_id=ThreadId(1) depth=0
2025-01-05T10:30:45.125Z DEBUG hedl_ffi::audit: FFI call parameters function="hedl_parse" params=[("input_len", "1024")]
2025-01-05T10:30:45.130Z INFO hedl_ffi::audit: FFI call completed function="hedl_parse" duration_ms=7.2 status="success"
Language Bindings
C
const char* input = "%VERSION: 1.0\n---\nname: Test\n";
HedlDocument* doc = NULL;
if
char* json = NULL;
if
;
C++
;
Python (ctypes)
=
# Define function signatures
=
=
=
=
=
=
=
=
# Use
= b
=
=
# HEDL_OK
=
=
=
Go (cgo)
package main
/*
#cgo LDFLAGS: -lhedl
#include "hedl.h"
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func Parse(input string) (string, error)
Ruby (FFI)
extend FFI::Library
ffi_lib
# Error codes
= 0
attach_function :hedl_parse, [:string, :int, :int, :pointer], :int
attach_function :hedl_to_json, [:pointer, :int, :pointer], :int
attach_function :hedl_get_last_error, [], :string
attach_function :hedl_free_document, [:pointer], :void
attach_function :hedl_free_string, [:pointer], :void
doc = FFI::MemoryPointer.new(:pointer)
result = hedl_parse(input, -1, 1, doc)
unless result == HEDL_OK
raise
end
doc_ptr = doc.read_pointer
return nil if doc_ptr.null?
json_ptr = FFI::MemoryPointer.new(:pointer)
result = hedl_to_json(doc_ptr, 0, json_ptr)
begin
if result == HEDL_OK
json_str = json_ptr.read_pointer.read_string
return json_str
else
raise
end
ensure
hedl_free_string(json_ptr.read_pointer)
hedl_free_document(doc_ptr)
end
end
end
Complete Function Reference
Parsing & Validation
int hedl_parse(const char *input, int input_len, int strict, HedlDocument **out_doc)int hedl_validate(const char *input, int input_len, int strict)
Format Conversion (HEDL → Other)
int hedl_to_json(const HedlDocument *doc, int include_metadata, char **out_str)int hedl_to_yaml(const HedlDocument *doc, int include_metadata, char **out_str)int hedl_to_xml(const HedlDocument *doc, char **out_str)int hedl_to_csv(const HedlDocument *doc, char **out_str)int hedl_to_parquet(const HedlDocument *doc, uint8_t **out_data, uintptr_t *out_len)int hedl_to_neo4j_cypher(const HedlDocument *doc, int use_merge, char **out_str)int hedl_to_toon(const HedlDocument *doc, char **out_str)
Format Conversion (Other → HEDL)
int hedl_from_json(const char *json, int json_len, HedlDocument **out_doc)int hedl_from_yaml(const char *yaml, int yaml_len, HedlDocument **out_doc)int hedl_from_xml(const char *xml, int xml_len, HedlDocument **out_doc)int hedl_from_parquet(const uint8_t *data, uintptr_t len, HedlDocument **out_doc)int hedl_from_toon(const char *toon, int toon_len, HedlDocument **out_doc)
Operations
int hedl_canonicalize(const HedlDocument *doc, char **out_str)int hedl_lint(const HedlDocument *doc, HedlDiagnostics **out_diag)int hedl_get_version(const HedlDocument *doc, int *major, int *minor)int hedl_schema_count(const HedlDocument *doc)int hedl_alias_count(const HedlDocument *doc)int hedl_root_item_count(const HedlDocument *doc)
Diagnostics
int hedl_diagnostics_count(const HedlDiagnostics *diag)int hedl_diagnostics_get(const HedlDiagnostics *diag, int index, char **out_str)int hedl_diagnostics_severity(const HedlDiagnostics *diag, int index)
Memory Management
void hedl_free_string(char *s)void hedl_free_document(HedlDocument *doc)void hedl_free_diagnostics(HedlDiagnostics *diag)void hedl_free_bytes(uint8_t *data, uintptr_t len)
Error Handling
const char *hedl_get_last_error(void)const char *hedl_get_last_error_threadsafe(void)void hedl_clear_error_threadsafe(void)
Zero-Copy Callbacks
int hedl_to_json_callback(const HedlDocument *doc, int include_metadata, HedlOutputCallback callback, void *user_data)int hedl_to_yaml_callback(const HedlDocument *doc, int include_metadata, HedlOutputCallback callback, void *user_data)int hedl_to_xml_callback(const HedlDocument *doc, HedlOutputCallback callback, void *user_data)int hedl_to_csv_callback(const HedlDocument *doc, HedlOutputCallback callback, void *user_data)int hedl_to_neo4j_cypher_callback(const HedlDocument *doc, int use_merge, HedlOutputCallback callback, void *user_data)int hedl_canonicalize_callback(const HedlDocument *doc, HedlOutputCallback callback, void *user_data)
Async Operations
HedlAsyncOp *hedl_parse_async(const char *input, int input_len, int strict, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_lint_async(const HedlDocument *doc, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_canonicalize_async(const HedlDocument *doc, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_json_async(const HedlDocument *doc, int include_metadata, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_yaml_async(const HedlDocument *doc, int include_metadata, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_xml_async(const HedlDocument *doc, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_csv_async(const HedlDocument *doc, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_neo4j_cypher_async(const HedlDocument *doc, int use_merge, HedlCompletionCallback callback, void *user_data)HedlAsyncOp *hedl_to_toon_async(const HedlDocument *doc, HedlCompletionCallback callback, void *user_data)void hedl_async_cancel(HedlAsyncOp *op)void hedl_async_free(HedlAsyncOp *op)
Performance Characteristics
Overhead: C FFI adds minimal overhead vs native Rust. Essentially zero-cost abstraction for parsing and conversion.
Memory: Same as Rust implementation. No additional allocations beyond necessary conversions.
Thread Safety: Functions are thread-safe. Error messages use thread-local storage. Documents are NOT thread-safe by design and should not be shared across threads without external synchronization.
Callback Performance: Zero-copy streaming via callbacks avoids full buffering. Suitable for large outputs (>1MB).
Async Performance: Non-blocking async operations suitable for I/O-bound tasks. Work-stealing thread pool for efficient parallelism.
Building and Testing
Building the FFI Library
# Build with all format support
# Build with specific formats
# Build C header
Testing
# Run all FFI tests
# Run with logging
RUST_LOG=hedl_ffi::audit=debug
Dependencies
hedl-core- Core parser implementationhedl-lint- Linting enginelibc0.2 - C standard library bindingstracing0.1 - Structured logging- Various format libraries (json-ld, serde_yaml, etc.) behind feature gates
License
Apache-2.0
Contributing
Contributions welcome! Please ensure:
- All example code compiles and runs
- Tests pass with logging enabled
- Function signatures match hedl.h
- Memory management is correct (no leaks in examples)