Skip to main content

Native Bindings Workflow

This document describes how to use Zynx to generate and manage bindings for existing C libraries, with a focus on ffi modules and bindgen integration.


Overview

Zynx automates much of the C interop workflow:

  1. Compile local C sources into a static library.
  2. Generate Zynx bindings from C headers.
  3. Install the results into modules/<name>/<target>/ for use in your workspace.

Use this workflow when:

  • you maintain C sources and headers directly in your project, or
  • you vendor a C library into your repository and want tight, explicit bindings.

ffi Modules

Use ffi modules when the C source and headers live directly in your project.

zynx add mylib --kind ffi --header mylib.h --source mylib.c
zynx sync mylib

This workflow:

  1. Compiles the local C sources into a static archive.

  2. Generates Zynx bindings from the headers using bindgen.

  3. Places the artifacts under:

    modules/<name>/<target>/
    lib<name>.a
    <generated-bindings>.zx
    .cache/ # bindgen and build metadata

Typical outputs for the example above:

  • modules/mylib/<target>/libmylib.a
  • generated .zx bindings for mylib.h
  • bindgen report data under modules/mylib/<target>/.cache/

You can then import the generated bindings from your Zynx code via their emitted module path.


Bindgen Flags

bindgen is used internally by ffi modules, and it can also be invoked directly.

When used via ffi modules:

  • Flags passed via --clang-arg are forwarded to the internal Clang parser.
  • cFlags configured on an ffi module are applied when compiling the local C source files and when parsing headers, ensuring consistent macro definitions and include paths.

When used directly, you can call:

zynx bindgen -o bindings.zx \
--clang-arg -Iinclude \
--clang-arg -DMODE=1 \
mylib.h

Key options:

  • -o <out.zx> – Output file for generated Zynx declarations.
  • --clang-arg <arg> – Additional flags for Clang (includes, defines, warning options).
  • --report <path> – Optional report of skipped or partially translated symbols.

For most projects, prefer ffi modules to keep compilation and binding generation in a single, declarative place. Use direct bindgen calls when you need more ad‑hoc or experimental binding generation.


Generated Type Shapes

bindgen preserves C surface types in two primary ways:

  • Tagged records and enums
    Emitted as @c_import + @native declarations so that:

    • field and variant layout matches the C ABI,
    • ordering and alignment are preserved.
  • Non‑tag typedefs
    Emitted as transparent type aliases when the target type is representable in Zynx.

Examples:

type Size = usize;
type PointPtr = *Point;
type PointArray = Point[2];

This keeps C typedef names visible in Zynx signatures without changing ABI or runtime layout. Call sites can use the familiar C names without paying any runtime cost.

Where C types cannot be represented directly (for example, exotic bitfield patterns), bindgen will fall back to the closest safe representation or omit the symbol, depending on configuration. Use --report to inspect these cases.


Struct ABI Compatibility

For tagged C structs, Zynx types generated by bindgen use @native. In Zynx, @native also implies strict field order for layout, which ensures:

  • Declaration order is preserved.
  • The compiler does not reorder or re‑pack fields.
  • The in‑memory layout remains compatible with the C ABI.

As a result:

  • Passing such structs by value or reference across the Zynx/C boundary is ABI‑safe, as long as both sides agree on the platform and compiler configuration.
  • You should not add additional attributes that change layout (@packed, custom alignment) on top of generated declarations unless you also update the C side accordingly.

When in doubt, keep generated ABI surface types unchanged and layer higher‑level Zynx types on top if you need different ergonomics.


Package Interop Boundary

C interop and package management are related but distinct concerns:

  • package
    Zynx libraries resolved from registries or git. They are consumed via the package system and lockfile.

  • ffi
    Local C code that must be compiled and bound into the current project, typically living in the same repository as your Zynx workspace.

Registry and git package dependencies are designed for Zynx code built from source (or provided as binary artifacts). They are not a replacement for ffi modules:

  • Use package for Zynx library projects resolved by semver.
  • Use ffi for local C code that must be compiled and bound into the current project.

A typical setup is:

  • One or more Zynx packages for higher‑level logic and reusable libraries.
  • One or more ffi modules that provide bindings to platform libraries or vendored C code.
  • Packages that depend on ffi modules at the workspace level, not the other way around.

Keeping this boundary clear makes builds more predictable and keeps ABI‑sensitive C integration localized to well‑defined modules.