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:
- Compile the module as position-independent code.
- Link it as a shared library.
- Generate metadata from the exported header.
- 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 (
sectionortrailer) - 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:
- the source file directory
- the working directory
zynx.native.dynamic.searchPaths- CLI
--lib-path
Example project configuration:
{
"zynx": {
"native": {
"dynamic": {
"searchPaths": ["plugins"],
"loader": {
"resolve": "lazy",
"visibility": "local"
}
}
}
}
}
Recommended Pattern
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
.dllruntime 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.v1is not finalized yet.