New Array behavior in Ruby

1. Surprise

Array.new(5, []) returns a new array with 5 [] elements, but those elements are actually references to the same object:

irb> a = Array.new(5, [])
#=> [[], [], [], [], []]
irb> a.first << ?c
#=> ["c"]
irb> a
#=> [["c"], ["c"], ["c"], ["c"], ["c"]]

This is confirmed by looking at the underlying ids:

irb> a.map(&:object_id)
#=> [69987008691280,
#    69987008691280,
#    69987008691280,
#    69987008691280,
#    69987008691280]
irb> a.map(&:object_id).uniq!
#=> [69987008691280]

2. Solution

The correct way of generating a list of distinct object is to pass [] to a block. The block is evaluated for each new element in the array:

irb> a = Array.new(5) { [] }
#=> [[], [], [], [], []]
irb> a.first << ?c
#=> ["c"]
irb> a
#=> [["c"], [], [], [], []]
irb> a.map(&:object_id)
#=> [69987008612720,
#    69987008612700,
#    69987008612680,
#    69987008612660,
#    69987008612640]
irb> a.map(&:object_id).uniq!
#=> nil

3. How it works

[] was returning a new object on each call:

irb> b = []
#=> []
irb> a = Array.new(5) { b }
#=> [[], [], [], [], []]
irb(main):010:0> a.first << ?c
#=> ["c"]
irb> a
#=> [["c"], ["c"], ["c"], ["c"], ["c"]]
irb> a.map(&:object_id)
#=> [69987008484780,
#    69987008484780,
#    69987008484780,
#    69987008484780,
#    69987008484780]
irb> a.map(&:object_id).uniq!
#=> [69987008484780]

Categories:

Updated: