Clojure Time Bomb 💣
Recently we ran into an issue in our Nextjournal Clojure codebase which was hard to reproduce and it took a while to see what was going on. Let me show what we found:
The symptom was a
"clojure.lang.Var$Unbound cannot be cast to .." error which only happened in our CI or when we restarted the Clojure app.
It turned out, we were using a
declare’d var inside a
def which blows up at some point later even if the var has
def’d by then. A trivial example:
While this is reasonable, the error message
"clojure.lang.Var$Unbound cannot be cast to .." is not necessarily the most helpful one...
What is happening?
The Clojure compiler reads and evaluates a Clojure file one top level form at a time. So at the time
bomb is used, but not yet
def’d (unbound), the
box map contains a value representing the then unbound var.
Defining the var later doesn’t change it in
In a REPL-driven workflow, one would run into this error only once. This is because when
bomb has been defined once, the error will not occur again until the Clojure process is restarted. The error goes away when just re-running the code cell above. This made it a little bit tricky to understand what was going on.
Don’t use a
declare’d var within a
def in Clojure.