try-let
is a Clojure macro designed to make handling some exceptions slightly nicer. It acts like let
, but allows you to catch exceptions which may be thrown inside the binding vector. Exceptions thrown inside the body of the try-let
are deliberately ignored.
try-let
is in Clojars. To use it in a Leiningen project, add it to your project.clj dependencies:
then require try-let
in your code:
(ns my.example
(:require [try-let :refer [try-let]]))
It can be quite difficult to combine try/catch
with let
properly. Clojure pushes you towards one of two patterns, neither of which is ideal.
(try
(let [value (func-that-throws)]
(act-on-value value))
(catch Exception e
(log/error e "func-that-throws failed")))
In the above pattern, the scope of the try/catch
is too great. In addition to func-that-throws
, it also affects act-on-value
.
(let [value
(try (func-that-throws)
(catch Exception e (log/error e "func-that-throws failed")))]
(act-on-value value))
In the above pattern, the scope of the try/catch
is correct, affecting only func-that-throws
, but when an exception is caught, act-on-value
is evaluated regardless and must handle the exceptional case when value
is nil.
With try-let
, we can instead do:
(try-let [value (func-that-throws)]
(act-on-value value)
(catch Exception e
(log/error e "func-that-throws failed")))
This allows the scope of the try/catch
to be made as precise as possible, affecting only func-that-throws
, and for evaluation to only proceed to act-on-value
when value
is obtained without error. In this way, try-let
can be thought of as similar to if-let
, where the body is only evaluated when the value of the binding vector is not nil.
You can have multiple catch
stanzas for different exceptions. Much of what you'd expect to work in a normal let
works:
(try-let [val-1 (risky-func-1)
val-2 (risky-func-2 val-1)]
(log/info "using values" val-1 "and" val-2)
(* val-1 val-2)
(catch SpecificException _
(log/info "using our fallback value instead")
123)
(catch RuntimeException e
(log/error e "Some other error occurred")
(throw e))
(finally
(release-some-resource)))
As an alternative, you can also put catch
stanzas before other body expressions:
(try-let [val-1 (risky-func-1)]
(catch Exception e
(log/error e "Problem calling risky-func-1")
0)
(try-let [val-2 (risky-func-2 val-1)]
(catch Exception e
(log/error e "Problem calling risky-func-2")
0)
(log/info "using values" val-1 "and" val-2)
(* val-1 val-2)))
This makes the code logic more linear, where exceptions are handled closer to where they appear.
There is also a try+-let
macro which is compatible with slingshot-style catch
stanzas.
Copyright © 2015-2019 rufoa
Distributed under the Eclipse Public License, the same as Clojure.