chris finne

freeze - I can Modify CONSTANTS in Ruby

25 Mar 2012

STOP THE PRESSES!

I can modify a CONSTANT!

Whoa, that’s a bit scary. I’ve been coding in Ruby for 5 or 6 years now. I probably knew this before and forgot. After all, when I was first learning Ruby, I read Programming Ruby and this concept is likely in there (but i’m too lazy to confirm it right now). I know I’m making myself look like an idiot for going this long without fully internalizing (or forgetting) this, but oh well. I’m writing this for my own benefit. I won’t forget this concept after spending a while explaining this important Ruby concept.

At least recently, I’ve been mentally assuming that I’d get an ugly warning when I’d modify a constant:

>> FOO='bar'
=> "bar"
>> FOO << '-more-bar'
=> "bar-more-bar"

The warning I was expecting was this when I try to redefine a constant:

>> FOO='bar'
=> "bar"
>> FOO='not bar'
(irb):2: warning: already initialized constant FOO
=> "not bar"

But technically the first example isn’t redefining it is modifying the CONSTANT. Let’s look at this again using object_id:

>> FOO='bar'
=> "bar"
>> FOO.object_id
=> 2157596880
>> # Modify it
>> FOO << '-more-bar'
=> "bar-more-bar"
>> FOO.object_id
=> 2157596880
>> # Redefine it
>> FOO='not bar'
(irb):5: warning: already initialized constant FOO
=> "not bar"
>> FOO.object_id
=> 2157574820

Note that the object_id’s changed when we redefined but not when we modified. Redefining is what triggers the CONSTANT warning.

But I recently did something like this:

>> ARR=[1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> qwer=ARR
=> [1, 2, 3, 4, 5]
>> qwer.delete(3)
=> 3

Then later on I used ARR again, and lo and behold it was missing the 3. YIKES!!!

Let’s look at this again using object_id

>> ARR=[1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> ARR.object_id
=> 2157583000
>> qwer=ARR
=> [1, 2, 3, 4, 5]
>> qwer.object_id
=> 2157583000
>> qwer.delete(3)
=> 3

Ok, that makes sense. qwer is just another pointer to the same object.

So what now?

How about both an offensive and defensive strategy. First the defense:

If you don’t want it modified, then freeze it

>> ARR=[1,2,3,4,5].freeze
=> [1, 2, 3, 4, 5]
>> qwer=ARR
=> [1, 2, 3, 4, 5]
>> qwer.delete(3)
TypeError: can't modify frozen array
	from (irb):10:in `delete'
	from (irb):10

And for the offense use dup to create a duplicate object of the CONSTANT:

>> ARR=[1,2,3,4,5].freeze
=> [1, 2, 3, 4, 5]
>> ARR.object_id
=> 2157590400
>> qwer=ARR.dup
=> [1, 2, 3, 4, 5]
>> qwer.object_id
=> 2157575680
>> qwer.delete(3)
=> 3

dup creates a duplicate object

Great, but…

>> FOO=42
=> 42
>> bar=FOO
=> 42
>> FOO.object_id
=> 85
>> bar.object_id
=> 85
>> bar += 99
=> 141
>> bar.object_id
=> 283

Oh yeah, in Ruby, Fixnum and Float aren’t ever modified, they are immutable. When you do operations on them you are just reassigning another Fixnum or Float to your variable, so dup and freeze are not needed.