Why Hash.new { [] } stays empty after <<
Question
What's the return value of the following Ruby code?
h = Hash.new { [] }
h[:a] << 42
h[:b] << 84
h # => ???
The correct answer is
-
{ a: 42, b: 84 } -
{ b: 84 } -
{ a: { b: [42, 84] } } -
{}Correct
Explanation
TL;DR
Hash.new { [] } runs the block on every missing-key lookup and returns the block's result, a brand-new empty array. The block never assigns anything into the hash, so h[:a] << 42 appends 42 to a throwaway array that is immediately discarded. No key is ever created and h returns {}.
Step by step
Each lookup of a missing key calls the block again and gets a different array:
h = Hash.new { [] }
h[:a] # => []
h[:a].equal?(h[:a]) # => false
So the two appends each mutate a fresh array that nothing references afterward:
h = Hash.new { [] }
h[:a] << 42 # => [42]
h[:b] << 84 # => [84]
h # => {}
h[:a] # => []
The << calls return [42] and [84], proof that the append itself worked, but Hash#[] on a missing key only computes a default. It does not store it. The arrays become garbage as soon as the expression ends.
The working idiom
To build a hash of arrays, the block must take its two arguments (hash, key) and perform the assignment itself:
h = Hash.new { |hash, key| hash[key] = [] }
h[:a] << 42
h[:b] << 84
h # => {a: [42], b: [84]}
Here the first lookup of :a assigns the new array into the hash before returning it, so << mutates an array the hash actually holds.
Share this quiz
Comments
No comments yet. Be the first.