Skip to content

pjstadig/assertions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

assertions

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.

License

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.