How constant are your constants?
When an object should not change during the execution of a program, you make it a constant:
Good forever, right? Well, not really. More accurately, perhaps, not at all. First, there is the case of a simple reassignment:
Ruby issues a warning, but the constant still gets reassigned. This is perhaps unfortunate, but at least it’s clear what happened. In other cases, the change of a constant can take place by less obvious means and, to boot, without raising any warnings.
What’s going on? The critical thing to understand is that constants’ intended
use it to keep the object reference unchanged. This is why Ruby issues a
warning when a constant is reassigned to point to another object. However, the
object referenced by the constant is free of any constraint or
supervision from Ruby. Since the bang version of .map
changes the referenced array in
place, Ruby sees no violation of the constant’s “constantness”.
The above example is rather contrived, while the change of the constant’s value still happens in an obvious fashion. Here’s a version of it that is both more realistic and less transparent:
Jim was supposed to get a polite greeting but got a familiar “HELLO THERE”
instead. This happened because when Joe called .greet_enthusiastically
, the
variable greeting
was made to point to the same "Hello"
string object
referenced by the constant GREETING
. Via this new reference, the string had
“there” appended to it and then upcased, all in place. The end result of this
was that the class variable was irreversibly modified by a poorly designed
instance method.
The problem of unintentionally changing a constant’s value can be mitigated somewhat by Ruby’s object freezing. However, this freezing is shallow and applies to the object itself but not to its components. Again, using an array as an example:
While the array [[1, 2], 3]
referenced by the constant could not be modified,
its constituent array referenced by ary
remained free of any such
constraints. Any of Ruby’s methods that modify an object in
place will produce this effect.
This post was heavily inspired by the description of Ruby constants’ behaviour in this post from Bear Metal.