Escaping Clojure Exceptions
Given the following code, it’s pretty obvious that we’ve handled the
inevitable exception and we’re going to return :handled
, right?
(defn barf [_]
(throw (Exception. "We've got bugs!")))
(try
(map barf [:a :b])
(catch Exception e :handled))
Let’s try it:
1. Unhandled java.lang.Exception
We've got bugs!
REPL: 69 collbox.core/barf
core.clj: 2726 clojure.core/map/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
RT.java: 525 clojure.lang.RT/seq
core.clj: 137 clojure.core/seq
core_print.clj: 53 clojure.core/print-sequential
core_print.clj: 160 clojure.core/fn
MultiFn.java: 233 clojure.lang.MultiFn/invoke
main.clj: 243 clojure.main/repl/read-eval-print
...
Wait, what? We took care to catch that exception! Somehow it’s
escaping our try
/ catch
and leaking out. What’s happening here?
There’s actually a good hint right there in the stacktrace, if you
read carefully: clojure.lang.LazySeq
. The try
block is actually
returning a lazy sequence, which means it’s carrying unevaluated code
which can be evaluated at a later time to define a sequence. This
code isn’t executed until the REPL tries to display the sequence, at
which point it has to force evaluation. At this point, however, the
code is no longer executing within the context of try
. (Props if you
actually saw that coming.)
The solution in this case is quite simple: wrap the expression
returning the lazy sequence in a call to doall
to force it to be
fully evaluated (within the try
):
(try
(doall (map barf [:a :b]))
(catch Exception e :handled))
;; => :handled
Much better.
For experienced Clojurists there’s nothing too surprising here, but
the behavior is a bit counter-intuitive, so it’s good to be reminded
that this can happen. This definitely can come up in practical
contexts like generating API responses, and it’s less obvious with
multiple layers of function calls between the try
and the map
.
There’s no silver bullet here, so any time you’re catching exceptions, ask yourself—could this be lazy? If so, force the sequence to be realized or exceptions could escape.