Skip to content

Instantly share code, notes, and snippets.

@seanhandley
Created August 12, 2014 15:43
Show Gist options
  • Save seanhandley/3bf9011ac0428bda4b49 to your computer and use it in GitHub Desktop.
Save seanhandley/3bf9011ac0428bda4b49 to your computer and use it in GitHub Desktop.
Ruby Hash Constants are Anything But Constant
irb(main):001:0> I_AM_A_CONSTANT = "I never change"
=> "I never change"
irb(main):002:0> i_am_a_variable = I_AM_A_CONSTANT
=> "I never change"
irb(main):003:0> i_am_a_variable = "I can change"
=> "I can change"
irb(main):004:0> I_AM_A_CONSTANT
=> "I never change"
irb(main):005:0> I_AM_A_CONSTANT_HASH = {contents: "I never change"}
=> {:contents=>"I never change"}
irb(main):006:0> variable_hash = I_AM_A_CONSTANT_HASH
=> {:contents=>"I never change"}
irb(main):007:0> variable_hash[:contents] = "zomg!"
=> "zomg!"
irb(main):008:0> I_AM_A_CONSTANT_HASH
=> {:contents=>"zomg!"}
irb(main):009:0>
@caius
Copy link

caius commented Aug 12, 2014

Technically all the constant does is make sure it doesn't get pointed at another object id. It doesn't make sure the object isn't mutated.

Your proof with a string is flawed, you're reassigning the i_am_a_variable rather than mutating the object. (Subtle diff!) A better example of that would be:

I_AM_A_CONSTANT = "string"
i_am_a_variable = I_AM_A_CONSTANT

i_am_a_variable << " mutated!!111oneone"

i_am_a_variable # => "string mutated!!111oneone"
I_AM_A_CONSTANT # => "string mutated!!111oneone"

Which then makes the hash mutation seem less surprising, you're mutating the object both the constant and variable have a reference to. If you want to stop accidental mutations of constants (or objects elsewhere) in this way, you need to freeze them:

# With a string
I_AM_A_CONSTANT = "string".freeze
i_am_a_variable = I_AM_A_CONSTANT

i_am_a_variable << " mutated!!111oneone"
# ~> -:4:in `<main>': can't modify frozen String (RuntimeError)

# With a hash
I_AM_A_CONSTANT = {things: "here"}.freeze
i_am_a_variable = I_AM_A_CONSTANT

i_am_a_variable[:moar] = "THINGS"
# ~> -:4:in `<main>': can't modify frozen Hash (RuntimeError)

And when you bump up against the frozen hash, you can just #dup it into the variable to make sure the copy of it is mutatable, and the original is left alone (which I think you discovered anyway.)

I_AM_A_CONSTANT = {things: "here"}.freeze
i_am_a_variable = I_AM_A_CONSTANT.dup

i_am_a_variable[:moar] = "THINGS"
i_am_a_variable # => {:things=>"here", :moar=>"THINGS"}
I_AM_A_CONSTANT # => {:things=>"here"}

@seanhandley
Copy link
Author

Thanks @caius - I'm aware of the underpinnings and the way references work and my production code is already using #dup and #freeze.

What sucks is that these things are referred to as "constants" by all documentation and examples like this, contrived as they are, serve to show why people in functional languages care so much about immutability.

I love Ruby, but I imagine the thousands of bugs introduced to production codebases by this easily misinterpreted behaviour and my mind boggles. They shouldn't be called constants, they need a different name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment