Splatting a frozen Hash with [*h]
Question
What is the return value of the following Ruby code?
h = { a: 21, b: 42 }.freeze
[*h] # => ???
The correct answer is
-
It raises
FrozenError -
[{ a: 21, b: 42 }] -
[:a, 21, :b, 42] -
[[:a, 21], [:b, 42]]Correct
Explanation
TL;DR
The splat operator * expands an object inside an array literal by calling its to_a method. Hash#to_a returns a new array of [key, value] pairs, so [*h] returns [[:a, 21], [:b, 42]]. The hash itself is never mutated, so freeze is irrelevant and no FrozenError is raised.
Step by step
Hash#to_a builds a new array; the receiver is left untouched:
h = { a: 21, b: 42 }.freeze
h.to_a # => [[:a, 21], [:b, 42]]
h # => {a: 21, b: 42}
[*h] splats that array's elements into the surrounding array literal:
h = { a: 21, b: 42 }.freeze
[*h] # => [[:a, 21], [:b, 42]]
Each [key, value] pair stays grouped: the splat flattens one level only, so the result is an array of pairs, not [:a, 21, :b, 42].
Under the hood
The splat does not know anything about hashes. It calls to_a on its operand and expands the resulting elements. You can watch it happen with a probe object:
class Probe
def to_a
puts "to_a called"
[1, 2, 3]
end
end
[*Probe.new] # => [1, 2, 3]
Output:
to_a called
An object without to_a is simply wrapped, and nil.to_a returns [], which is why splatting nil produces an empty expansion:
[*42] # => [42]
[*nil] # => []
Edge cases
The double splat ** is the hash-flavored sibling: it expands key-value pairs into a hash literal (via to_hash) instead of an array:
h = { a: 21, b: 42 }.freeze
{ **h, c: 84 } # => {a: 21, b: 42, c: 84}
Both operators build new containers, so both work fine on frozen input. To go the other way, Array#to_h reassembles an array of pairs into a hash: [[:a, 21], [:b, 42]].to_h returns { a: 21, b: 42 }.
Share this quiz
Comments
No comments yet. Be the first.