Maintenance of Slingshot has moved to clj-commons since the original maintainer, Stephen Gilardi, has passed away. Issues and PRs from his old Slingshot repo will be addressed here over the next few releases.
The library coordinates have changed from slingshot/slingshot to
org.clj-commons/slingshot. The namespaces have changed to have a
clj-commons. prefix, e.g. clj-commons.slingshot.
Provides try+ and throw+. Each is 100% compatible with Clojure
and Java's native try and throw both in source code and at
runtime. Each also provides additional capabilities intended to
improve ease of use by leveraging Clojure's features like maps,
records, and destructuring.
Clojure's native try and throw behave much like those of Java:
throw can accept objects derived from java.lang.Throwable and try
selects from among catch clauses based on the class of the thrown
object.
In addition to fully supporting those uses (whether they originate
from Clojure code or from Java code via interop), try+ and
throw+ provide these enhanced capabilities:
-
throw+can throw any Java object, not just those whose class is derived fromjava.lang.Throwable.Clojure maps or records become an easy way to represent custom exceptions without requiring
gen-class. -
catchclauses withintry+can catch:- any Java object thrown by
throw+, - any map passed to
ex-infoand thrown bythroworthrow+, or - any
Throwablethrown by Clojure'sthrow, or Java'sthrow.
The first catch clause whose selector matches the thrown object will execute.
a selector can be:
-
a class name: (e.g.,
RuntimeException,my.clojure.record), matches any instance of that class, or -
a key-values vector: (e.g.,
[key val & kvs]), matches objects where(and (= (get object key) val) ...), or -
a predicate: (function of one argument like
map?,set?), matches any Object for which the predicate returns a truthy value, or -
a selector form: a form containing one or more instances of
%to be replaced by the thrown object, matches any object for which the form evaluates to truthy. -
the class name, key-values, and predicate selectors are shorthand for these selector forms:
`<class name> => (instance? <class name> %)` `[<key> <val> & <kvs>] => (and (= (get % <key>) <val>) ...)` `<predicate> => (<predicate> %)`
- any Java object thrown by
-
the binding to the caught exception in a catch clause is not required to be a simple symbol. It is subject to destructuring so the body of the catch clause can use the contents of a thrown collection easily.
-
in a catch clause, the context at the throw site is accessible via the hidden argument
&throw-context. -
&throw-contextis a map containing:for Throwable caught objects:
:object the caught object; :message the message, from .getMessage; :cause the cause, from .getCause; :stack-trace the stack trace, from .getStackTrace; :throwable the caught object;for non-Throwable caught objects (including maps passed to ex-info)
:object the caught object; :message the message, from throw+ or ex-info; :cause the cause, from throw+ or ex-info, see below; :stack-trace the stack trace, captured by throw+ or ex-info; :wrapper the Throwable wrapper that carried the object; :throwable the outermost Throwable whose cause chain contains the wrapper, see below.
To throw a non-Throwable object, throw+ wraps it in a
Throwable wrapper by calling ex-info. Every instance of
IExceptionInfo (whether generated by throw+ or by a direct call to
ex-info) is treated as a wrapper.
The wrapper is available via the :wrapper key in &throw-context.
user=> (defn foo [x]
#_=> (throw (ex-info "important message" {:value x})))
#_=>
#_=> (defn -main []
#_=> (try+
#_=> (foo 5)
#_=> (catch Object e
#_=> (println "caught" e &throw-context))))
#'user/foo
#'user/-main
user=> (-main)
caught {:value 5} {:object {:value 5}, :message important message, :cause nil,
:stack-trace #object[[Ljava.lang.StackTraceElement...],
:wrapper #error {
:cause important message
...},
:throwable #error {...}}Between being thrown and caught, a wrapper may be further wrapped by
other Exceptions (e.g., instances of RuntimeException or
java.util.concurrent.ExecutionException). try+ sees through all
such wrappers to find the thrown object. The outermost wrapper is
available within a catch clause a via the :throwable key in
&throw-context.
When throw+ throws a non-Throwable object from within a try+
catch clause, the outermost wrapper of the caught object being
processed is captured as the cause of the new throw+. This can be
overridden by providing an explicit cause argument to throw+.
- an optional
elseclause may appear after allcatchclauses and before anyfinallyclause. Its contents will be executed (for side effects) immediately after the code in thetry+body completes only if nothing was thrown.
(try+
(throw+ "Boom!")
(catch string? s (println "caught string:" s))
(else (println "no exception"))
(finally (println "always prints!")))
;; caught string: Boom!
;; always prints!
(try+
"Success!"
(catch string? s (println "caught string:" s))
(else (println "no exception"))
(finally (println "always prints!")))
;; no exception
;; always prints!deps.edn:
org.clj-commons/slingshot {:mvn/version "0.13.0"}project.clj:
[org.clj-commons/slingshot "0.13.0"]tensor/parse.clj:
(ns tensor.parse
(:require [clj-commons.slingshot :refer [throw+]]))
(defn parse-tree [tree hint]
(if (bad-tree? tree)
(throw+ {:type ::bad-tree :tree tree :hint hint})
(parse-good-tree tree hint)))math/expression.clj:
(ns math.expression
(:require [tensor.parse]
[clojure.tools.logging :as log]
[clj-commons.slingshot :refer [throw+ try+]]))
(defn read-file [file]
(try+
[...]
(tensor.parse/parse-tree tree)
[...]
(catch [:type :tensor.parse/bad-tree] {:keys [tree hint]}
(log/error "failed to parse tensor" tree "with hint" hint)
(throw+))
(catch Object _
(log/error (:throwable &throw-context) "unexpected error")
(throw+))))Based on clojure.contrib.condition, data-conveying-exception, discussions on the clojure mailing list and wiki and discussions and implementations by Steve Gilardi, Phil Hagelberg, and Kevin Downey.
Copyright © 2011-2025 Stephen C. Gilardi, Kevin Downey, and Phil Hagelberg; additional work after the move to clj-commons by Sean Corfield.
Distributed under the Eclipse Public License, the same as Clojure.