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 https://mozilla.org/MPL/2.0/. This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.