hedl-ffi
C ABI bindings for HEDL. Use the HEDL parser from C, C++, Python (ctypes/cffi), Ruby (FFI), Go (cgo), or any language with C interop.
Why This Exists
HEDL is implemented in Rust, but most systems need to integrate with code written in other languages. Rather than maintaining separate parser implementations (with inevitable drift and bugs), this crate exposes the Rust implementation through a stable C interface. The bindings add minimal overhead while preserving memory safety guarantees through explicit ownership and defensive checks.
Building
This produces libhedl.so on Linux, libhedl.dylib on macOS, or hedl.dll on Windows.
To generate the C header file:
Basic Usage
Parsing and Converting
const char* hedl_str = "%V:2.0\n---\nname: Test\nvalue: 42\n";
HedlDocument* doc = NULL;
// Parse with strict mode enabled (the 1 parameter)
int result = ;
if
// Convert to JSON
char* json = NULL;
if
;
The -1 for input_len tells the parser to use strlen() internally. Pass the actual length if you have binary data or want to avoid the extra scan.
Validation Without Parsing
If you only need to check whether input is valid HEDL:
int result = ;
if
This is faster than full parsing when you do not need the resulting document.
Format Conversions
The library supports bidirectional conversion with JSON, YAML, XML, CSV, Parquet, Neo4j Cypher, and TOON. Each format is behind a feature flag, so you only pay for what you use.
// HEDL to other formats
;
;
;
;
// Other formats to HEDL
;
;
;
Streaming Large Outputs
When converting large documents, buffering the entire output in memory is wasteful. Use the callback variants to stream output directly to a file or network socket:
void
FILE* fp = ;
;
;
All conversion functions have callback variants: hedl_to_json_callback, hedl_to_yaml_callback, hedl_to_xml_callback, hedl_to_csv_callback, hedl_to_neo4j_cypher_callback, and hedl_canonicalize_callback.
Linting
HedlDiagnostics* diags = NULL;
if
Document Inspection
int major = 0, minor = 0;
;
int schemas = ;
int aliases = ;
int roots = ;
Error Handling
All functions return an integer error code. Zero means success; negative values indicate specific failure modes:
| Code | Name | Meaning |
|---|---|---|
| 0 | HEDL_OK |
Success |
| -1 | HEDL_ERR_NULL_PTR |
NULL pointer passed where valid pointer expected |
| -2 | HEDL_ERR_INVALID_UTF8 |
Input contains invalid UTF-8 sequences |
| -3 | HEDL_ERR_PARSE |
HEDL syntax error |
| -4 | HEDL_ERR_CANONICALIZE |
Canonicalization failed |
| -5 | HEDL_ERR_JSON |
JSON conversion failed |
| -6 | HEDL_ERR_ALLOC |
Memory allocation failed |
| -7 | HEDL_ERR_YAML |
YAML conversion failed |
| -8 | HEDL_ERR_XML |
XML conversion failed |
| -9 | HEDL_ERR_CSV |
CSV conversion failed |
| -10 | HEDL_ERR_PARQUET |
Parquet conversion failed |
| -11 | HEDL_ERR_LINT |
Linting failed |
| -12 | HEDL_ERR_NEO4J |
Neo4j Cypher conversion failed |
| -13 | HEDL_ERR_TOON |
TOON conversion failed |
| -14 | HEDL_ERR_REENTRANT_CALL |
Reentrant call from callback |
| -15 | HEDL_ERR_CANCELLED |
Async operation was cancelled |
| -16 | HEDL_ERR_QUEUE_FULL |
Async work queue is full |
| -17 | HEDL_ERR_INVALID_HANDLE |
Invalid async operation handle |
Error Messages
Error messages are stored in thread-local storage, so each thread maintains independent error state. This means you can safely call HEDL functions from multiple threads without locking:
const char* error = ;
; // Clear after handling
Memory Management
The library uses explicit ownership: every pointer returned by HEDL must be freed with the matching free function. There are four types of allocations:
| Returned by | Free with |
|---|---|
hedl_to_* string functions |
hedl_free_string() |
hedl_parse, hedl_from_* |
hedl_free_document() |
hedl_lint |
hedl_free_diagnostics() |
hedl_to_parquet |
hedl_free_bytes(data, len) |
Input strings (the HEDL/JSON/YAML you pass in) remain owned by the caller. The library does not hold references to them after the function returns.
Use-After-Free Protection
The library uses poison pointer detection to catch common mistakes:
;
int result = ; // Returns HEDL_ERR_NULL_PTR, not a crash
This is a safety net, not a substitute for correct code. Do not rely on it.
UTF-8 Validation
All string inputs are validated for UTF-8 correctness before processing. Invalid sequences return HEDL_ERR_INVALID_UTF8. All outputs are guaranteed to be valid UTF-8.
Logging
The library uses the tracing crate for structured logging. Set the RUST_LOG environment variable to enable:
Logs include function entry/exit with timing, sanitized parameters (pointer addresses are masked), and success/failure outcomes. Useful for debugging integration issues or tracking performance in production.
Language-Specific Examples
C++
Wrap the C API in RAII for automatic cleanup:
;
Python (ctypes)
=
=
=
=
=
=
=
=
= b
=
=
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
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)
raise unless hedl_parse(input, -1, 1, doc) == 0
doc_ptr = doc.read_pointer
json_ptr = FFI::MemoryPointer.new(:pointer)
begin
raise unless hedl_to_json(doc_ptr, 0, json_ptr) == 0
json_ptr.read_pointer.read_string
ensure
hedl_free_string(json_ptr.read_pointer)
hedl_free_document(doc_ptr)
end
end
end
API Reference
The full API is documented in the generated hedl.h header. Key function groups:
Parsing: hedl_parse, hedl_validate
Conversion to other formats: hedl_to_json, hedl_to_yaml, hedl_to_xml, hedl_to_csv, hedl_to_parquet, hedl_to_neo4j_cypher, hedl_to_toon
Conversion from other formats: hedl_from_json, hedl_from_yaml, hedl_from_xml, hedl_from_parquet, hedl_from_toon
Operations: hedl_canonicalize, hedl_lint, hedl_get_version, hedl_schema_count, hedl_alias_count, hedl_root_item_count
Diagnostics: hedl_diagnostics_count, hedl_diagnostics_get, hedl_diagnostics_severity
Memory: hedl_free_string, hedl_free_document, hedl_free_diagnostics, hedl_free_bytes
Errors: hedl_get_last_error, hedl_clear_error_threadsafe
Callbacks: All hedl_to_* functions have _callback variants for streaming output
Async: All major operations have _async variants with hedl_async_cancel and hedl_async_free for lifecycle management
Thread Safety
Functions are thread-safe. You can call any function from any thread without synchronization. Error messages are stored in thread-local storage, so concurrent errors do not interfere.
Documents themselves are not thread-safe. Do not share an HedlDocument* across threads without your own synchronization. In practice, parse on one thread and use on that same thread.
Testing
License
Apache-2.0