The shared default array in Hash.new([])
Question
What's the return value of the following Ruby code?
h = Hash.new([])
h[:a] << 42
h[:b] # => ???
The correct answer is
-
[] -
[nil] -
[42]Correct -
nil
Explanation
TL;DR
Hash.new([]) stores one single array as the default object. Every missing-key lookup returns that same array, and nothing is ever assigned into the hash. h[:a] << 42 therefore mutates the shared default in place, and h[:b], another missing key, returns that same mutated array: [42].
Step by step
h = Hash.new([])
h.default # => []
h[:a] # => []
h[:a] << 42 # => [42]
h # => {}
h.default # => [42]
h[:b] # => [42]
h[:a] does not create the key :a. Hash#[] on a missing key just returns the default object, here the one array passed to Hash.new. << 42 then appends to that array in place. The hash itself stays empty, but its default object now contains 42, so every missing key appears to hold [42]:
h = Hash.new([])
h[:a] << 42
h[:b].equal?(h[:c]) # => true
h.keys # => []
One object, many aliases. Any key you have never written to reads through to it.
The working idiom
To give each key its own array, use the block form and assign inside the block:
h = Hash.new { |hash, key| hash[key] = [] }
h[:a] << 42
h[:a] # => [42]
h[:b] # => []
h # => {a: [42], b: []}
The block runs once per missing key, creates a fresh array, and stores it under that key before returning it. Hash.new(object) is only safe when the default is immutable (0, nil, a frozen string); with a mutable default like an array or hash, every key shares the same object.
Share this quiz
Comments
No comments yet. Be the first.