Skip to main content

Building Dynamic Modules

This guide covers the producer side of Zynx dynamic modules: what you can export, how metadata is embedded, and how consumers load the result with require(...).

For the language surface itself, see Dynamic Modules.

Producer Model

A dynamic module is a shared library that exports Zynx ABI symbols plus embedded zynx.meta.v1 metadata.

The embedded metadata is what makes require("...") typed:

  • it records the logical module name
  • it records exported members and their ABI types
  • it records target and ABI compatibility information
  • it carries the generated header source used by the compiler

Consumers do not use sibling .zxh files anymore for require(). The metadata must be embedded in the shared library.

Export Rules

Today, the supported dynamic ABI surface is intentionally small:

  • export fn ...
  • export const ...

Example producer module:

export fn add(a: i32, b: i32) -> i32 {
return a + b;
}

export const ANSWER: i32 = 42;

Current non-goals for dynamic consumption:

  • exported generic callable APIs
  • exported types used directly through a dynamic handle
  • writable exported globals

If you need to expose a richer API, export concrete wrapper functions around your internal types.

Build Flow

The current workflow is:

  1. Compile the module as position-independent code.
  2. Link it as a shared library.
  3. Generate metadata from the exported header.
  4. Embed the metadata into the shared library.

On Linux, post-link embedding writes a real .zynx_meta ELF section.

On macOS, the preferred section-backed path is to emit the metadata blob first and pass it to the linker as a __DATA,__zynx_meta section.

zynx meta

Use zynx meta to inspect or materialize dynamic-module metadata.

Inspect a shared library:

zynx meta plugins/libmath.so

Typical output includes:

  • resolved path
  • module name
  • metadata storage kind (section or trailer)
  • schema version
  • target triple
  • ABI hash
  • export table summary

Embed metadata into an existing shared library:

zynx meta --embed=build/libmath.zxh build/libmath.so

If the shared-library path already has a recognized suffix, --embed can usually infer the adjacent header path.

Emit the raw metadata blob for linker-driven section injection:

zynx meta --emit-blob=build/libmath.meta build/libmath.zxh

This is the recommended path on macOS when creating a real __DATA,__zynx_meta section during linking.

Consumer Search Paths

Consumers load dynamic modules with string-literal paths:

const math = try require("plugins/libmath.so");

Those paths are resolved against:

  1. the source file directory
  2. the working directory
  3. zynx.native.dynamic.searchPaths
  4. CLI --lib-path

Example project configuration:

{
"zynx": {
"native": {
"dynamic": {
"searchPaths": ["plugins"],
"loader": {
"resolve": "lazy",
"visibility": "local"
}
}
}
}
}

Document and test the named-handle form in consumers:

fn run() -> i32! {
const math = try require("plugins/libmath.so");
let sum = try math.add(20, 22);
let answer = try math.ANSWER;
return sum + answer;
}

That keeps load failures, symbol failures, and normal API failures explicit at the call site.

Current Limitations

  • Windows .dll runtime support is not implemented yet.
  • Generic callable exports are rejected for dynamic ABI use.
  • Dynamic globals are read-only.
  • Exported types are described in metadata but are not consumable through require() as normal type names.
  • Schema evolution beyond zynx.meta.v1 is not finalized yet.