5

When I initialize a Ruby hash with a default value like 0 and create a new entry in the hash and increment it behaves as expected:

irb(main):001:0> h1 = Hash.new(0)
=> {}
irb(main):002:0> h1[:foo] += 1
=> 1
irb(main):003:0> h1
=> {:foo=>1}
irb(main):004:0> h1[:foo]
=> 1

Notice how h1 #=> {:foo=>1} and h1[:foo] #=> 1. That's what I was expecting to see.

Except if I use a default value of an empty array then this is what happens:

irb(main):005:0> h2 = Hash.new([])
=> {}
irb(main):006:0> h2[:foo] << "cats"
=> ["cats"]
irb(main):007:0> h2
=> {}
irb(main):008:0> h2[:foo]
=> ["cats"]

Notice how h2 #=> {} and h2[:foo] #=> ["cats"]. I don't know why this is happening.

  • What is going on here?
  • Why does h2 look like an empty hash but then still has a value with the key :foo?

If I use some a block then the expected behavior happens:

irb(main):001:0> h3 = Hash.new {|hash, key| hash[key] = [] }
=> {}
irb(main):002:0> h3[:foo] << "cats"
=> ["cats"]
irb(main):003:0> h3
=> {:foo=>["cats"]}
irb(main):004:0> h3[:foo]
=> ["cats"]

Notice how h2 #=> {:foo=>["cats"]} and h3[:foo] #=> ["cats"]. That's what I was expecting to see.

mbigras
  • 7,664
  • 11
  • 50
  • 111

1 Answers1

2

The answer is in Hash.new. In short:

If obj ([] in your case) is specified, this single object will be used for all default values. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block's responsibility to store the value in the hash if required.

Initialise with a block to set non-nil defaults.

t56k
  • 6,769
  • 9
  • 52
  • 115