Ruby's shallow copies of hashes

When you try to copy a Ruby hash using .dup or .clone, you get what is called a “shallow” copy. The data in the hash below the first level just seems to be referenced, so if you have a hash within the hash, and try to change a value in the deeper hash, the value is changed even for the original hash you ran the .dup on. To get a full (“deep”) copy of a hash, you have to run an inelegant hack using Marshal to copy it: copy_hash = (Marshal.load(Marshal.dump(source_hash))). This apparently applies to arrays too. See the code below for an example, and I hope this saves you hours of debugging.

# Ruby unexpected behavior when using .clone or .dup on a hash (or array)

# create a hash and freeze it so it shouldn't be modified
MY_HASH = { :one => { :first => 'eins', :second => 'zwei' } }.freeze

puts MY_HASH.inspect # {:one=>{:first=>"eins", :second=>"zwei"}}

new_hash = MY_HASH.dup # copy the hash, unfrozen

new_hash[:one][:second] = 'dos'

puts new_hash.inspect # {:one=>{:first=>"eins", :second=>"dos"}}

# original hash should not be modified, but it is!
puts MY_HASH.inspect # {:one=>{:first=>"eins", :second=>"dos"}}

# this happens apparently because hash copies are "shallow" and only
# contain pointers to values
# a "deep" copy requires ugliness using Marshal

MY_HASH2 = { :one => { :first => 'eins', :second => 'zwei' } }.freeze

puts MY_HASH2.inspect # {:one=>{:first=>"eins", :second=>"zwei"}}

new_hash2 = Marshal.load(Marshal.dump(MY_HASH2))

new_hash2[:one][:second] = 'dos'

puts new_hash2.inspect # {:one=>{:first=>"eins", :second=>"dos"}}

# now the original hash is intact:
puts MY_HASH2.inspect # {:one=>{:first=>"eins", :second=>"zwei"}}