lume

20 — Interop: using the whole world’s libraries without losing Lume’s core

Goal. Let Lume reuse the existing universe of libraries — C, C++, Rust, Go, Zig, Swift, and anything that compiles to WebAssembly — without rewriting them and without compromising Lume’s pure, zero-C, reproducible, capability-safe core. Interop is an opt-in layer, never part of the self-hosted core; a program that uses it declares the capability and accepts the trade-off.

The key insight: speak one lingua franca, not every language

There is no stable ABI for rich types (C++ templates, Rust/Go generics, Go’s runtime/GC), so you cannot consume another language’s rich type system directly. But every systems language can expose a C ABI (extern "C", #[no_mangle] extern "C", Go -buildmode=c-shared, Zig, Swift’s C interop). So the reach comes from two universal bridges, not from N language-specific importers:

Bridge Reaches Strengths Costs
C ABI + linking everything exposing a C interface (C, C++, Rust, Go, Zig, Swift, …) native speed, full system access unsafe, unsandboxed, breaks source-reproducibility
WebAssembly sandbox everything that compiles to WASM (Rust, C/C++, Go, …) sandboxed, capability-controlled, portable, deterministic call overhead, not every library targets WASM

“Compatible with everything” means everything that exposes a C ABI or compiles to WASM — roughly all that matters. Rich foreign types are always crossed through a C or WASM boundary; that is a property of the world’s missing universal ABI, not a Lume limitation.

How it works in Lume

Implementation status (#191).

  1. extern "c" declarations (shipped). Declare a foreign function with its C signature; 05_codegen.src emits the unboxing/boxing trampoline in the System V AMD64 convention and lasm emits the ELF relocation / PLT call.
    @link("libc.so.6")
    extern "c" fn puts(s: str) -> int
    
  2. Linking (shipped). lume.toml [ffi] libs = [...] (requiring "ffi" in [capabilities] allow) and lume build --link <lib.so|lib.a> link against .so (via lasm’s dynamic ELF64 writer: PT_INTERP, PT_DYNAMIC, DT_NEEDED, .dynsym, .dynstr, .got.plt, .rela.dyn, PLT) or static .a archives.
  3. WASM host (model / planned). std.wasm models linear memory, fuel, and capability grants in Lume and enforces "wasm"/"fs"/"net" capabilities at compile time ([E0200]); executing arbitrary binary .wasm modules in-process remains planned.

AI-first binding generation (the Lume advantage)

Rust has bindgen, Zig has @cImport; Lume preserves an explicit boundary contract. Run lume adapt c <header.h> -o <adapter.src> to generate extern "c" declarations plus explicit foreign_<name> adapters, or use --json (equivalently lume agent bind) for the machine-readable manifest. The accepted fragment contains int64_t scalars, borrowed NUL-terminated const char * strings, and adjacent const char *name, int64_t name_len read-only buffer pairs. A sized-buffer adapter accepts only the Lume string and passes count(string) as the C length, so a caller cannot provide an inconsistent length. The pointer is borrowed for the call only; no ownership transfer or post-call retention is inferred.

Every generated return remains unverified[int] and requires validation before trusted use. JSON manifests include the header’s real SHA-256, per-parameter ownership/extent metadata, the ffi capability, and foreign return provenance. These checks establish the generated call boundary, not the foreign library’s internal memory safety or its compliance with const and no-retention contracts.

Parsing is deliberately strict: one complete prototype per line, int64_t returns, no macros or conditional preprocessing, no duplicate symbols or parameter names, and no trailing tokens. Any error rejects the whole header with [I0003], produces no partial source, returns a nonzero CLI status, and leaves an existing -o file unchanged. Mutable/output buffers, arbitrary pointers, callbacks, variadics, other C types, HTTP/OpenAPI adapters, and adaptive MMIO remain open in #334.

Safety model: interop reuses the A3 provenance mechanism

Foreign code can crash, leak, and ignore Lume’s ownership and effects, so every boundary is guarded by the machinery Lume already has:

The purity boundary (be honest)

FFI with native linking breaks Lume’s zero-C, reproducible-from-source, self-contained guarantees: the program now depends on external binaries present at build/run time. Therefore:

Optional, Zig-style: a ffi/c capability could bundle a C front-end to compile C source dependencies directly (@cImport-like). Powerful, but it reintroduces C into that program’s build, so it is strictly opt-in and never in the pure core.

The reverse direction

Interop is two-way (shipped, #191): Lume exposes a C ABI (pub extern "c" fn … / @export("C")) and compiles to .so / .a / .wat / .wasm modules alongside a generated .h header (lume build -o libfoo.so|libfoo.a|foo.wat|foo.wasm), so C, Python, Node, Rust, and Go can call verified Lume libraries directly.

Track I — interop (Phase 4/5)

This is not core and lands after the language is systems-capable (ownership, effects, concurrency from Phase 3). Bricks:

Bottom line

Lume does not need to rewrite the world, and it does not need to borrow another language’s ecosystem to exist — it already self-hosts independently. Interop is the pragmatic bridge: speak the C ABI and host WASM, generate the bindings with the AI, and guard every foreign value with the same provenance/capability model built for AI output. The world’s libraries enrich Lume while the core stays pure, reproducible, and safe.