Zig-aware defining forms append z to the Clojure defining form they mirror.
defn -> defnz
def -> defz
defrecord -> defrecordz
deftype -> deftypez
defenum -> defenumz
This keeps the Clojure concept recognizable and marks Zig participation consistently.
defnzdefnz defines a normal Clojure Var/function backed by Zig.
(defnz add
"Adds two signed integers."
[x :i64
y :i64
:ret :i64]
"return x + y;")
It should preserve the familiar defn rhythm:
(defnz name
docstring?
attr-map?
signature-vector
zig-body)
The signature vector is the full boundary contract.
The signature vector contains argument binding/type pairs and a final return marker.
[arg type
arg type
:ret return-type]
Example:
[x :i64
y :i64
:ret :i64]
Rules:
:ret is required.:ret must be at the end.& remains reserved for Clojure-style rest args if a future version supports them.The :ret marker deliberately resembles keyword markers in Clojure binding forms, such as :as in destructuring.
The body is real Zig source, not a DSL.
(defnz sum-f64
[xs [:slice :const :f64]
:ret :f64]
"
var total: f64 = 0;
for (xs) |x| {
total += x;
}
return total;
")
The generated wrapper provides ergonomic Zig locals such as xs: []const f64. Inside the body, Zig should be free to use normal Zig features.
A body may instead live in a real .zig file, named with a {:zig/file "name.zig"} descriptor in place of the string. The file holds a complete Zig function the generated wrapper calls, keeping editor and zig fmt support; the same descriptor can link C libraries so the body may @cImport a C header. See ADR 26 and ADR 27.
The body may be omitted entirely. A defnz with a signature but no body takes its body from the pub fn of the same name in the .zig file co-located with the namespace's source, the same stem as the .clj:
;; body: the pub fn hypotenuse in app/geometry.zig, beside app/geometry.clj
(defnz hypotenuse
[a :f64
b :f64
:ret :f64])
The Clojure namespace is the Zig namespace: shared imports, helpers, and types live once in that file, and zig-deps declares the namespace's C link flags. A kebab-case name maps to its snake_case pub fn. An optional //! clj-zig: <ns> first-line header asserts the file belongs to the namespace. A body file may @import sibling and subdirectory .zig files. See ADR 28 and ADR 29.
The signature may be omitted as well:
;; signature inferred from app/geometry.zig's pub fn hypotenuse
(defnz hypotenuse)
A Zig type fixes the shape of each argument and the return, so the boundary contract is read straight from the pub fn prototype. Policy that a Zig type cannot express is the exception: a returned []T carries no ownership rule and a returned *T is a pointer or an opaque handle by the caller's choice, so a function returning either needs an explicit signature stating [:owned ...] or [:handle ...]. Inferring one reports :clj-zig/contract-policy-needed instead of guessing.
defzdefz defines Zig-side declarations that are not directly Clojure-callable.
(defz helpers
"
fn clamp(x: f64, lo: f64, hi: f64) f64 {
return if (x < lo) lo else if (x > hi) hi else x;
}
")
Mental model:
defz stays inside Zig
defnz crosses the Clojure/Zig boundary
deftypez defines a Zig-compatible boundary type.
(deftypez Point
[x :f64
y :f64])
defrecordz defines both a Clojure record and a Zig-compatible layout contract.
(defrecordz Point
"A 2D point shared between Clojure and Zig."
[x :f64
y :f64])
defenumz defines an enum bridge.
(defenumz ParseStatus
[ok 0
invalid 1
eof 2])
The return type is part of the signature contract.
:ret :i64 ;; returns a Clojure integer
:ret :f64 ;; returns a Double
:ret :bool ;; returns a Boolean
:ret :void ;; returns nil
Composite returns require explicit layout and ownership rules.
(defrecordz Point
[x :f64
y :f64])
(defnz midpoint
[a Point
b Point
:ret Point]
"...")
If Point is a defrecordz, returning Point should produce a normal Clojure record.
Clojure destructuring is relevant, but should happen on the Clojure side before the native call.
(defnz distance
[{x1 :x y1 :y} {:x :f64 :y :f64}
{x2 :x y2 :y} {:x :f64 :y :f64}
:ret :f64]
"
const dx = x1 - x2;
const dy = y1 - y2;
return @sqrt(dx * dx + dy * dy);
")
This lets Clojure accept map-shaped values while Zig receives native scalar values.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |