Clojure that speaks C

Jolt is a self-hosted Clojure implementation that runs on a portable Scheme layer that supports multiple backends. Chez natively, Gambit for JavaScript, no JVM. Most Clojure runs unchanged, any C library binds natively, and jolt build ships a single standalone binary.

  • Native C interop: defcfn turns C functions into ordinary Clojure fns
  • jolt build emits a single standalone binary with C dependencies linked in statically
  • Persistent data structures, lazy seqs, transducers, multimethods, and protocols with full Clojure semantics
  • Real concurrency: future, agent, pmap, and core.async on OS threads

The terminal on the right is jolt itself — compiled to JavaScript by the Gambit backend — evaluating live in your browser. It runs a reduced build to keep the download small, so regex is left out here and says so if you reach for it.

jolt via Gambit → JS repl
user=> (->> (range 10) (filter even?) (map #(* % %)) (reduce +))
120
user=>

Why Jolt?

Native C FFI

Bind any C shared library: declare it, wrap functions with defcfn, call them with Clojure values. The db library reaches SQLite and Postgres this way; http-client binds OpenSSL and zlib.

Standalone Binaries

jolt build ahead-of-time compiles a project — runtime, standard library, app, and its deps — into a single self-contained executable, with C dependencies linked statically. No Chez, no JVM, no source needed to run it.

Self-Hosted Compiler

Reads Clojure, analyzes it to a host-neutral IR, emits Scheme, and runs it — on Chez natively, or on Gambit compiled to JavaScript. The compiler is written in Clojure and compiles itself; Chez runs interpreted and compiled code identically, so the REPL and a built binary behave the same.

Real Concurrency

future/promise/agent/pmap run on OS threads over a shared heap, matching JVM semantics. core.async provides channels and go blocks.

Persistent Data Structures

Immutable vectors (32-way tries), cons lists, and HAMT maps/sets with Clojure value semantics. Transients are real mutable scratch collections.

Clojure-Compatible

Lazy/infinite seqs, transducers, destructuring, multimethods, protocols/records, metadata, namespaces, runtime eval, and the full reader.

Quick Start

Install the self-contained jolt binary — it bundles the runtime, compiler, and standard library, so there's nothing else to install.

# Homebrew
brew install jolt-lang/jolt/jolt

# or the install script (Linux / macOS)
curl -sL https://raw.githubusercontent.com/jolt-lang/jolt/main/install | bash

jolt -e '(+ 1 2)'        # => 3

Or run from a clone (needs Chez Scheme) — no build step, the bootstrap seed is checked in:

git clone --recurse-submodules https://github.com/jolt-lang/jolt.git
cd jolt
jolt -e '(+ 1 2)'        # => 3

Usage

Evaluate an expression

$ jolt -e '(->> (range 10) (filter even?) (map (fn [x] (* x x))) (reduce +))'
# 120
$ jolt -e '(/ 1 2)'
# 1/2

Run a project

jolt run -m myapp.core   # resolve deps.edn, then run -main
jolt -M:test [args]      # run an alias's :main-opts
jolt path                # print the resolved source roots

Compile a standalone binary

jolt build -m myapp.core -o myapp   # single self-contained executable
./myapp arg1 arg2                        # runs anywhere; args reach -main
# --opt for the optimized build, --dev for an unoptimized one

Bind a C library

(require '[jolt.ffi :as ffi])
(ffi/load-library "libsqlite3.dylib")  ; or declare :jolt/native in deps.edn

(ffi/defcfn sqlite3-libversion "sqlite3_libversion" [] :string)
(sqlite3-libversion)             ; => "3.51.0"

Any C library binds the same way — the native interop guide covers types, memory, structs, and static linking.

nREPL

jolt --nrepl-server      # auto-resolves deps.edn, writes .nrepl-port
# connect CIDER / Calva / Cursive via the .nrepl-port file, then
# redefine a var and the next call sees it — no restart

Develop against a live process: see REPL-Driven Development.

Read the documentation for installation, writing libraries, and C interop.

Differences from Clojure

Jolt targets Clojure semantics but runs on Scheme, not the JVM. Most portable Clojure runs unchanged — persistent collections, the numeric tower, lazy seqs, transducers, multimethods, protocols/records, atoms, future/agent/pmap, core.async, runtime eval, and the full reader all behave as on the JVM. The genuine divergences:

AspectDifference
Java interopNo JVM, so no general Java interop, reflection, or gen-class/proxy — a native C FFI takes its place, plus a shimmed subset of java.*
BigDecimalNot available (decimal? is always false); the rest of the numeric tower matches
RegexCompiled by irregex, not java.util.regex — common patterns work, some Java-specific features differ