Build the smallest useful proof of the Clojure developer experience.
defnz scalar function -> compile Zig -> load native artifact -> call from Clojure
defnz slice function -> pass primitive array/buffer -> read or mutate in Zig
The proof of concept will not include:
- Zig calling Clojure;
- embedded JVM from Zig;
- arbitrary Clojure data marshalling;
- production AOT packaging;
- cross-platform polish beyond one development target.
These ideas came out of the original Clojure/Zig discussion. They remain interesting but sit beyond the proof of concept. They are recorded so they are not lost, not scheduled.
- Bidirectional interop: Zig calling back into Clojure, making the relationship two-way rather than Clojure-to-Zig only.
- Embedded Clojure from Zig: Zig hosting a JVM and the Clojure runtime to invoke Clojure functions through JNI or a similar interface.
- Code as data across the boundary: passing Clojure code representations to an embedded Clojure runtime for evaluation, leaning on homoiconicity. Conceptually possible, but considerably more complex than ordinary function calls.
- Richer data protocol: a serialization format or a negotiated interop protocol for exchanging rich, nested Clojure structures, should a concrete need outgrow explicit per-function contracts. See ADR 17.
Authoring:
defnz
defz
deftypez
defrecordz
defenumz
Inspection:
zig/signature
zig/spec
zig/source
zig/generated-source
zig/library
zig/recompile!
zig/explain
Pure data functions:
zig/normalize-type
zig/normalize-signature
zig/generate-source
- Clojure project skeleton.
- Signature parser and normalizer.
- Type parser and normalizer.
defnz macro over normalized specs.- Zig source generation for scalar functions.
- Zig compiler invocation.
- Content-addressed artifact cache.
- Native loading through JDK FFM/Panama.
- Scalar invocation.
- Const and mutable slice invocation.
- REPL inspection helpers.
- Structured diagnostics.
clj-zig/
deps.edn
README.md
src/
clj-zig/
core.clj
signature.clj
type.clj
spec.clj
source.clj
compile.clj
cache.clj
ffm.clj
diagnostics.clj
inspect.clj
test/
clj-zig/
signature_test.clj
type_test.clj
source_test.clj
scalar_test.clj
slice_test.clj
diagnostics_test.clj
examples/
scalar.clj
slices.clj
Signature:
- Parses scalar signatures.
- Requires final
:ret. - Rejects misplaced
:ret. - Preserves argument binding names.
Types:
- Normalizes scalar Zig keyword types.
- Normalizes const slices.
- Normalizes mutable slices.
- Rejects unknown types.
- Rejects malformed compound vectors.
Generation:
- Generates readable Zig source.
- Generates stable symbol names.
- Generates pointer-plus-length wrappers for slices.
Compilation:
- Compiles a scalar function.
- Writes generated source and manifest.
- Reuses cached artifacts when unchanged.
- Rebuilds when the body changes.
Invocation:
- Calls
:i64 functions. - Calls
:f64 functions. - Converts
:void to nil. - Passes read-only primitive arrays as const slices.
- Passes mutable primitive arrays as mutable slices.
Diagnostics:
- Reports Zig compiler errors with generated source path.
- Keeps last good binding after failed redefinition.
- Exposes structured diagnostic data.
Prioritize:
- Clojure-like UX;
- data-oriented contracts;
- decomposition and composability;
- REPL resilience;
- excellent errors;
- inspectability;
- narrow native boundary.
Avoid:
- hiding Zig types;
- generic object marshalling;
- premature bidirectional runtime features;
- macro complexity that belongs in pure functions;
- overfitting the first implementation to one example.