Bidirectional Idris 2 to Zig FFI bridge - stable ABI for any Idris 2 project
Idris 2 compiles to multiple backends (Scheme, RefC, JavaScript). The RefC backend generates C code, but:
-
No stable ABI - Internal structures may change between versions
-
Manual memory management - Must understand Idris GC
-
Type marshalling - Converting Idris types to C types is tedious
-
Platform differences - C compilation varies by platform
This library solves all of these by wrapping the RefC output in Zig, providing:
-
Stable C ABI - Guaranteed interface that won’t break
-
Automatic memory management - Zig handles the bridge safely
-
Type-safe marshalling - Automatic conversion with compile-time checks
-
Cross-platform builds - One build.zig for all targets
-
WASM support - Compile to WebAssembly seamlessly
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
│ (Python, Rust, Go, JavaScript, etc.) │
└──────────────────────────┬──────────────────────────────────────┘
│ C ABI / WASM
▼
┌─────────────────────────────────────────────────────────────────┐
│ idris2-zig-ffi │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ C Header │ │ Zig Wrapper │ │ Memory │ │
│ │ Exports │ │ Layer │ │ Bridge │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ • Stable ABI guarantee • Type-safe marshalling │
│ • Version compatibility • Cross-platform builds │
│ • WASM/WASI support • Automatic cleanup │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Idris 2 RefC Output │
│ (Your verified Idris 2 library) │
└─────────────────────────────────────────────────────────────────┘In your Zig project’s build.zig.zon:
.dependencies = .{
.idris2_zig_ffi = .{
.url = "https://github.com/hyperpolymath/idris2-zig-ffi/archive/refs/tags/v0.1.0.tar.gz",
.hash = "...",
},
},# Compile Idris 2 to RefC
idris2 --cg refc -o mylib MyLib.idr
# This generates build/exec/mylib_app/ with C filesconst std = @import("std");
const ffi = @import("idris2_zig_ffi");
// Import your Idris-generated C code
const idris = @cImport({
@cInclude("mylib.h");
});
pub fn safeDiv(a: i64, b: i64) ?i64 {
// Call Idris function through FFI bridge
const result = ffi.call(idris.MyLib_safeDiv, .{ a, b });
return ffi.fromIdrisOption(i64, result);
}// Allocate memory that Idris GC can track
const ptr = ffi.alloc(T, count);
defer ffi.free(ptr);
// Create a managed string from Zig
const idris_str = ffi.toIdrisString("hello");
defer ffi.freeIdrisString(idris_str);
// Convert Idris string to Zig slice
const zig_str = ffi.fromIdrisString(idris_str);| Idris Type | Zig Type | Function |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Idris functions that return Either Error Value
const result = ffi.callChecked(idris.parseJson, .{json_str});
switch (result) {
.ok => |value| {
// Use value
},
.err => |e| {
std.log.err("Parse error: {s}", .{ffi.getErrorMessage(e)});
},
}// In build.zig
const wasm = b.addSharedLibrary(.{
.name = "mylib",
.root_source_file = .{ .path = "src/main.zig" },
.target = .{ .cpu_arch = .wasm32, .os_tag = .wasi },
});
wasm.addModule("idris2_zig_ffi", ffi_module);
// Export functions for JavaScript
export fn safeDiv(a: i64, b: i64) i64 {
return ffi.call(idris.safeDiv, .{ a, b }) orelse 0;
}| Platform | Architecture | Status |
|---|---|---|
Linux |
x86_64, aarch64 |
✓ Full support |
macOS |
x86_64, aarch64 |
✓ Full support |
Windows |
x86_64 |
✓ Full support |
WASM |
wasm32-wasi |
✓ Full support |
WASM |
wasm32-freestanding |
✓ Browser support |
FreeBSD |
x86_64 |
✓ Full support |
iOS |
aarch64 |
Experimental |
Android |
aarch64 |
Experimental |
-
proven - Verified safe operations library
See CONTRIBUTING.adoc.