Zero penalty runtime assertions for Clojure.




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



(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

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.