Zero penalty runtime assertions for Clojure.
Clojure’s built in assert macro is a compile-time construct that will
eliminate assertions if *assert* is set to false. However, there isn’t a
good way to set *assert* to false. You cannot set! it in your namespace,
because that will fail when your code is loaded in any context where there
isn’t an established binding for *assert* (AOT’ed code for one). Otherwise
you could try to alter-var-root *assert* in a user.clj file, but that
does not compose well. You can set a Java system property to configure some
features of the Clojure compiler, but there doesn’t exist a property for
disabling assertions.
Even if you could work that all out, what you have is still a compile-time assertion construct. Java’s assertions have the property that they can be enabled/disabled at runtime without any performance penalty.
My assertions work similar to Java’s. There is a pjstadig.Assertions class
with a static final field that is initialized by calling
desiredAssertionStatus for that class. When this static final field set to
false and it is used in a conditional, then the body of that conditional
(i.e. the assertion) is eliminated as dead code by the JIT.
To use this for a Leiningen project
[pjstadig/assertions "0.2.0"]
Or for a Maven project
<dependency> <groupId>pjstadig</groupId> <artifactId>assertions</artifactId> <version>0.2.0</version> </dependency>
Example:
(ns pjstadig.assertions.test (:refer-clojure :exclude [assert]) (:require [pjstadig.assertions :refer [assert]])) (defn -main [& [flag]] (assert (not= flag "fail")))
When you run the program with assertions enabled it throws an AssertionError:
~/src/assertions$ JVM_OPTS=-ea lein run -m pjstadig.assertions.test fail
Exception in thread "main" java.lang.AssertionError: (not= flag "fail")
at pjstadig.assertions.test$_main.doInvoke(test.clj:14)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.lang.Var.invoke(Var.java:415)
at user$eval33.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6619)
at clojure.lang.Compiler.eval(Compiler.java:6609)
at clojure.lang.Compiler.eval(Compiler.java:6582)
at clojure.core$eval.invoke(core.clj:2852)
at clojure.main$eval_opt.invoke(main.clj:308)
at clojure.main$initialize.invoke(main.clj:327)
at clojure.main$null_opt.invoke(main.clj:362)
at clojure.main$main.doInvoke(main.clj:440)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
When you run it with assertions disabled no AssertionError is thrown, and there is no penalty for including assertions in your code:
~/src/assertions$ lein run -m pjstadig.assertions.test fail ~/src/assertions$
You can also enable/disable assertions specifically for the pjstadig package
(e.g. -ea:pjstadig...), or the pjstadig.Assertions
(e.g. -ea:pjstadig.Assertions) class. However, doing so will enable/disable
assertions globally for any namespace that is using these assertions. There
is currently no way to enable/disable assertions for individual Clojure
namespaces.
Copyright © Paul Stadig and contributors. All rights reserved. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.