It is possible to set! to fields of a defrecord even though they are final.
(defprotocol SetA (seta [x a]))
=> SetA
(defrecord X [a]
SetA
(seta [this newa]
; Next line should error at compile time, does not.
; (However (set! a newa) does error correctly.)
(set! (.a this) newa)))
=> user.X
(def x (->X 0))
=> #'user/x
x
=> #user.X{:a 0}
(seta x 1) ;; This should not run.
=> 1
x
=> #user.X{:a 1}
There are two issues here:
# The Clojure compiler does not detect that (set! (.a this) x) is assignment to a final field. This could be enhanced. Nicola Mometto has discovered why and believes he has a straightforward patch.
# The JVM bytecode verifier only performs the necessary final-assignment check on classfiles version 9 and above:
https://bugs.openjdk.java.net/browse/JDK-8159215 (Clojure generates version 6 classfiles.) This is out of our hands.
*Approach:* make the compiler fail at compile time if trying to set! a field that's final
*Patch:* 0001-CLJ-2126-don-t-assign-final-fields.patch