Date: 2026-06-16
The boundary contract names [:error-union E T] and says a Zig error
should reach Clojure as data, but it leaves the mapping open. A Zig
error union is a tagged value with no stable C-ABI representation, so an
export fn cannot return E!T directly. The boundary needs a concrete
protocol for carrying the success value or the failure across the edge.
[:error-union E T] is supported in return position only. E is a Zig
error set named by symbol, including the builtin anyerror; T is a
value-carrying scalar or :void. The user body returns E!T and may
either return error.X or return a value.
clj-zig generates an inner function holding the user body and an export
wrapper that calls it with catch. On success the wrapper returns the
value (or nothing, for :void) and reports no error. On failure it
copies @errorName(e) into a caller-provided byte buffer and writes the
name's length to an out-parameter.
The Clojure side returns the value T on success and the error as a
keyword on failure, the error name keywordized. The error
error.InvalidCharacter becomes :InvalidCharacter. The error crosses
as data, not a thrown exception, so a caller branches on the result: a
keyword is the error, anything else is the value.
An error union in argument position is rejected when the spec is built,
with :clj-zig/unsupported-error-union, as is a value type that is
neither a scalar nor :void.
A failing native operation reads as ordinary Clojure data that composes
in if-let and cond, with no exception handling forced on the caller. The cost is that the result is a union the caller must
discriminate, and a T that could itself be a keyword would be
ambiguous; since T is a numeric or boolean scalar (or :void), this
cannot arise in the proof of concept. The out-parameter protocol also
means an error-union wrapper carries two synthetic parameters the
boundary contract does not name.
Throwing an ex-info on error was rejected because the contract calls
for error-as-data, and a keyword result composes more naturally in the
dynamic Clojure surface. Encoding the error as its @intFromError
integer was rejected because Clojure cannot map a code back to a name
without the compiled error table, whereas @errorName is portable.
Supporting error unions as arguments was rejected; the proof of concept
has no use for them, and the contract frames them as a return shape.
The value type T may now be a named enum or a named struct (scalar-only
or buffer-carrying), in addition to a scalar or :void. A named enum
crosses as its i32 backing per ADR 20, so its wire shape is unchanged:
the wrapper returns the backing int directly and the Clojure side maps it
back to the member keyword (an unknown int returns the raw int, total).
A named struct combines the existing error-name buffer and length
out-params with a struct out-pointer __ret: the inner __impl returns
E!NiceRecord, the export wrapper writes the error name and returns
WITHOUT writing the struct on failure, and on success writes the struct
field by field through __ret (reusing the wire extern struct a
result record already produces) and sets the length to zero.
The error path writes no struct and frees nothing; a body that allocates
before erroring would leak, so the supported discipline is to check the
error condition before any allocation. The success path of a
buffer-carrying struct emits a per-field __free shim (mirroring
:owned result records per ADR 21) that runs in a finally after the
Clojure side copies the bytes out, so a read fault cannot leak; a
scalar-only struct emits a no-op shim, uniform with the :owned path.
This extends, and does not reverse, the scalar-or-void decision above. The cost is a third out-parameter on the wire for the struct arm and a second exported symbol for its free shim; the caller still discriminates the result the same way (a keyword is the error, anything else is the value).
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 |