raylib-jnk Guide
User-facing documentation for raylib-jnk: 212 raylib examples ported to jank, a native Clojure dialect (C++/LLVM), not the JVM. Each page below covers one interop pattern or raylib API surface, citing the example file that proves it.
Why this exists
jank is young, and almost nothing has been written about using it against a real C library at this scale. Porting 212 raylib examples surfaced a set of interop rules that are not obvious from jank's own documentation and that cost real debugging time to find. Each page here is one of those rules, written up with the committed example that proves it, so the next person does not have to rediscover it by bisecting a failing draw loop.
What raylib-jnk is
212 of the official raylib examples (shapes, core, text, textures, shaders, models, and audio), each a small jank namespace under raylib-examples/src/raylib_examples/, each calling raylib's C API directly through (:include "raylib.h").
It is the native-Clojure sibling of b12n-raylib-jlt (raylib in Jolt/Chez Scheme) and of an unreleased JVM-Clojure port over coffi/Panama. All three bind the same C library directly; what differs is the boundary each language draws between its own values and C's:
jolt and JVM Clojure cross the FFI boundary at the call: an FFI call marshals values in and out, but a raylib struct can otherwise live anywhere a normal value lives. jank draws the boundary at the value itself: a native C++ value (
Color,Vector2,Model, ...) can be constructed and used inline, but it cannot cross a jank function boundary: not returned, not passed as a parameter, not carried throughloop/recur. Every pattern in this guide is a consequence of that one rule.
Four things follow from it:
- The value can't leave the form that produced it: construct inline, bind in an enclosing
let, or hold frame-crossing mutable state as an opaque box in a jank atom. (native-value-lifetimes.md) - The compiler enforces it at compile time, strictly:
if/condbranch type-checking, numeric coercion between jank and native number types, and struct construction all have sharp, well-defined rules. (type-checking-and-coercion.md) - A C-interop toolbox reaches everything the rule seems to block: pointer interop (
cpp/&,cpp/aget,cpp/unsafe-cast), out-params, native arrays, and shared jank helper namespaces. One genuine gap is left: a jank fn cannot become a C function pointer. (cpp-interop-toolbox.md) - The full raylib surface is reachable despite the rule: fonts, models and animations, audio, 3D mode, rlgl, and (platform-permitting) compute shaders all work, proven example by example. (
raylib-api-coverage.md)
Where that leaves the C. 5 cpp/raw blocks remain across 214 sources, down from 78, and every example that can be ported has been. Four are blocked on one real gap (a jank fn cannot become a C function pointer); the fifth, point_rendering, keeps its C deliberately, because porting it would misrepresent the CPU-versus-GPU comparison the example exists to show.
Nothing about jank's C++ interop is raylib-specific: (:include "header.h") and cpp/ reach any C/C++ library. This repo just happens to exercise it against one real, struct-heavy graphics API across 212 examples.
Capability pages
The interop core (the reason this repo is interesting)
native-value-lifetimes.md: the one rule that explains most crashes, frame-crossing mutable state viacpp/rawstatics (and the per-fn-static duplication gotcha), and create-once resources via outer-letcapture.type-checking-and-coercion.md:if/condbranch type-checking (including theand/orgotcha), the numeric-traps table (mod/quot/cpp/float/min/max), and constructing native structs from jank data.cpp-interop-toolbox.md: pointer interop (cpp/&,cpp/aget,cpp/unsafe-cast),int *out-params, native arrays, shared jank helper namespaces (rlights,shaders,models,rendertex), and the one construct that is genuinely blocked.numeric-performance.md: why a hot loop written the ordinary way is ~70x off the same loop throughcpp/operators, what that costs per iteration, and the two loop traps that come with the fix.
What's proven to work
raylib-api-coverage.md: fonts, models and animations, audio, 3D mode, rlgl + textures, and compute shaders (with the platform caveat; see the root README's Known limitations).jvm-surface-gaps.md: what replaces the missing JVM surface (Math/*,format, char literals), what's actually available (clojure.core/clojure.string, with caveats), and a few gotchas that save a recompile.
Orientation
getting-started.md: requirements, cloning with the submodule, and thebbtask surface.porting-workflow.md: the end-to-end process for porting one example: source of truth, file layout, the five-place registration, the headless smoke test.example-catalog.md: a tour of all 212 examples grouped by raylib category, and how to add one (now with a preview GIF per recorded example; seedocs/demos/README.mdfor the full gallery).raygui-to-keyboard.md: the pattern for porting raygui-based examples (sliders/checkboxes) to keyboard controls.
See also
b12n-raylib-jlt: the same idea in Jolt (Chez Scheme) overjolt.ffi. Its FFI boundary is per-call, not per-value: aCamera3Dcan live in an ordinary variable between FFI calls, unlike jank's native values.- An unreleased JVM-Clojure port over
coffi/Panama takes the same per-call boundary as Jolt, plus a garbage collector jank doesn't have to work around.