How does Clojure ^:const work?

I'm trying to understand what ^:const does in clojure. This is what the dev docs say. http://dev.clojure.org/display/doc/1.3

(def constants {:pi 3.14 :e 2.71})

(def ^:const pi (:pi constants)) (def ^:const e (:e constants))

The overhead of looking up :e and :pi in the map happens at compile time, as (:pi constants) and (:e constants) are evaluated when their parent def forms are evaluated.

This is confusing since the metadata is for the var bound to symbol pi, and the var bound to symbol e, yet the sentence below says it helps speed up the map lookups, not the var lookups.

Can someone explain the what ^:const is doing and the rationale behind using it? How does this compare to using a giant let block or using a macro like (pi) and (e)?


That looks like a bad example to me, since the stuff about map-lookup just confuses the issue.

A more realistic example would be:

(def pi 3.14)
(defn circ [r] (* 2 pi r))

In this case, the body of circumference is compiled into code that dereferences pi at runtime (by calling Var.getRawRoot), each time circumference is called.

(def ^:const pi 3.14)
(defn circ2 [r] (* 2 pi r))

In this case, circ2 is compiled into exactly the same code as if it had been written like this:

(defn circ2 [r] (* 2 3.14 r))

That is, the call to Var.getRawRoot is skipped, which saves a little bit of time. Here is a quick measurement, where circ is the first version above, and circ2 is the second:

user> (time (dotimes [_ 1e5] (circ 1)))
"Elapsed time: 16.864154 msecs"
user> (time (dotimes [_ 1e5] (circ2 1)))
"Elapsed time: 6.854782 msecs"

Besides the efficiency aspect described above, there is a safety aspect that is also useful. Consider the following code:

(def two 2)
(defn times2 [x] (* two x))
(assert (= 4 (times2 2)))    ; Expected result

(def two 3)                  ; Ooops! The value of the "constant" changed
(assert (= 6 (times2 2)))    ; Used the new (incorrect) value

(def ^:const const-two 2)
(defn times2 [x] (* const-two x))
(assert (= 4 (times2 2)))    ; Still works

(def const-two 3)            ; No effect!
(assert (= 3 const-two ))    ; It did change...
(assert (= 4 (times2 2)))    ; ...but the function did not.

So, by using the ^:const metadata when defining vars, the vars are effectively "inlined" into every place they are used. Any subsequent changes to the var, therefore, do not affect any code where the "old" value has already been inlined.

The use of ^:const also serves a documentation function. When one reads (def ^:const pi 3.14159) is tells the reader that the var pi is not ever intended to change, that it is simply a convenient (& hopefully descriptive) name for the value 3.14159.


Having said all the above, note that I never use ^:const in my code, since it is deceptive and provides "false assurance" that a var will never change. The problem is that ^:const implies one cannot redefine a var, but as we saw with const-two it does not prevent the var from being changed. Instead, ^:const hides the fact that the var has a new value, since const-two has been copied/inlined (at compile-time) to each place of use before the var is changed (at run-time).

A much better solution would be to throw an Exception upon attempting to change a ^:const var.