This document lists known differences between ClojureWasm and reference Clojure (JVM). ClojureWasm targets behavioral compatibility but diverges where JVM-specific features have no meaningful equivalent.
ClojureWasm supports multi-threaded concurrency via Zig's thread pool:
| Feature | Status | Notes |
|---|---|---|
| atom, volatile | Implemented | Thread-safe (atomic operations) |
| future, promise | Implemented | Zig std.Thread based |
| pmap, pcalls | Implemented | Chunked parallel dispatch |
| agent, send | Implemented | Per-agent serial queue |
| ref, dosync | Not implemented | STM out of scope (atom suffices) |
ClojureWasm has no JVM and cannot call Java classes. The following are permanently excluded:
.(dot operator),..,new,memfnreify,proxy,gen-class,gen-interface,definterfaceimport,bean,class?,supers,basesmonitor-enter,monitor-exit
Java standard library functionality is replaced with Zig equivalents:
| Java Pattern | ClojureWasm Equivalent |
|---|---|
(System/nanoTime) |
(__nano-time) |
(System/currentTimeMillis) |
(__current-time-millis) |
(System/getenv "KEY") |
(__getenv "KEY") |
(System/exit 0) |
(__exit 0) |
(Math/sqrt x) |
(__sqrt x) |
(Thread/sleep ms) |
(__sleep ms) |
(.getMessage e) |
(ex-message e) |
| Namespace | Reason |
|---|---|
| clojure.reflect | JVM reflection |
| clojure.inspector | JVM Swing UI |
| clojure.java.javadoc | JVM-specific |
| clojure.test.junit | JUnit integration |
| Namespace | Done | Total | Notes |
|---|---|---|---|
| clojure.core | 651 | 706 | 92% — see skip list below |
| clojure.core.protocols | 10 | 11 | CollReduce, IKVReduce |
| clojure.core.server | 7 | 11 | No socket REPL |
| clojure.instant | 3 | 5 | No Calendar/Timestamp |
| clojure.java.process | 5 | 9 | No async process |
| clojure.main | 16 | 20 | REPL, script loading |
| clojure.repl | 11 | 13 | No Java reflection |
| clojure.test | 38 | 39 | No spec integration |
| clojure.xml | 7 | 9 | Pure Clojure parser |
clojure.core.reducers (22), clojure.core.specs.alpha (1), clojure.data (5), clojure.datafy (2), clojure.edn (2), clojure.java.browse (2), clojure.java.io (19), clojure.java.shell (5), clojure.math (45), clojure.pprint (26), clojure.repl.deps (3), clojure.set (12), clojure.spec.alpha (87), clojure.spec.gen.alpha (54), clojure.stacktrace (6), clojure.string (21), clojure.template (2), clojure.test.tap (7), clojure.walk (10), clojure.zip (28), cljw.wasm (17), cljw.http (6).
compile, *compile-path*, *compile-files*, gen-class, gen-interface,
definterface, proxy, proxy-super, init-proxy, get-proxy-class,
construct-proxy, reify, memfn, bean, supers, bases, class?,
accessor, create-struct, defstruct, struct, struct-map
ref, alter, commute, ref-set, dosync, ensure,
ref-history-count, ref-min-history, ref-max-history
monitor-enter, monitor-exit, locking, add-classpath,
with-loading-context
import, import*, ., .., new, iterator-seq, enumeration-seq,
resultset-seq, vector-of
input-stream, output-stream, reader, writer, as-url
- Unified integers: ClojureWasm uses a single integer type (i48 in NaN
box, overflow to BigInt). JVM Clojure distinguishes Long, Integer, Short,
Byte.
(instance? Long x)should be replaced with(integer? x). - Unified floats:
double?andfloat?are equivalent (all floats are f64). JVM distinguishes Float and Double. - BigInt/BigDecimal/Ratio: Implemented with pure Zig (not java.math). Functionally compatible but internal representation differs.
case: Implemented ascondchain (O(n) linear scan). JVM Clojure compilescaseto a O(1) hash dispatch via thecase*special form.defn: No:inlinefunction support. Attribute map handling simplified.ns: No:gen-classsupport.extend-type: Special form, not a macro (no runtime expansion).
eduction: Returns an eager sequence. JVM Clojure returns an Eduction object implementing IReduceInit for lazy transducer application.iteration: Returns a lazy-seq. JVM Clojure returns a reified Seqable + IReduceInit.reduce-kv: Falls back to map entry sequence iteration. JVM Clojure uses the IKVReduce protocol for direct key-value reduction.nthnext/nthrest: Loop-based. JVM Clojure uses the IDrop interface for O(1) dropping on supported collections.
*report-counters*: Uses atom (not ref + dosync).report: Dynamic function (not multimethod).thrown?: Standalone macro. JVM Clojure dispatches through theassert-exprmultimethod insideis.deftest: Registers in an atom-based registry (not ns metadata).
clojure.pprint/pprint: Zig builtin implementation. Simplified compared to JVM's dispatch-based pretty printer.clojure.java.shell/sh: Uses Zigstd.process.Child(notRuntime.exec()). Functionally equivalent.clojure.stacktrace: Adapted for ClojureWasm's error model (no Java Throwable / StackTraceElement).clojure.walk: No IMapEntry / IRecord protocol checks.clojure.data: Type checks instead of protocol dispatch.- Regex: Built-in engine (not
java.util.regex). Core operations (re-find,re-matches,re-seq,re-groups) are compatible.
ClojureWasm supports exception type checking in catch clauses.
Exception, Throwable, and RuntimeException catch all thrown values.
Specific exception types (e.g., ExceptionInfo) are matched by the
__ex_type field on exception maps.
;; Both work:
(try (/ 1 0) (catch Exception e (ex-message e)))
(try (throw (ex-info "oops" {:k 1}))
(catch ExceptionInfo e (ex-data e)))JVM Clojure's deep class hierarchy (ArithmeticException, ClassCastException,
etc.) is not replicated; use Exception for general catches.
ex-info, ex-message, ex-data, and ex-cause work as expected.
- Verified: macOS Apple Silicon (aarch64-macos), Linux x86_64
- Cross-compiles: Linux aarch64, macOS x86_64
- Not tested: Windows, other architectures
- ARM64 JIT: Only active on aarch64; no-op on other architectures