1

I have performed the following test:

irb(main):023:0> a=[]
=> []

irb(main):024:0> b="1"
=> "1"

irb(main):025:0> a.push(b)
=> ["1"]

irb(main):026:0> a
=> ["1"]

irb(main):027:0> b="2"
=> "2"

irb(main):028:0> a
=> ["1"]

So far so good. Now, but as soon as I change b into a hash:

irb(main):012:0> a=[]
=> []
irb(main):013:0> b={}
=> {}
irb(main):014:0> b[:file]="one"
=> "one"
irb(main):015:0> a.push(b)
=> [{:file=>"one"}]
irb(main):016:0> a
=> [{:file=>"one"}]
irb(main):017:0> b[:file]="two"
=> "two"
irb(main):018:0> a
**=> [{:file=>"two"}]**

I didn't even push the b value into the a array. What happened here?

BroiSatse
  • 44,031
  • 8
  • 61
  • 86
meso_2600
  • 1,940
  • 5
  • 25
  • 50

2 Answers2

2

b is not a hash. It is a reference to a hash. And pushing a reference somewhere else won't make it point to a new instance.

Copy your hashes explicitly where you want to, using b.dup.

Sometimes even that might not be enough, as the same applies to values inside a hash: the new hash will have the same references inside, it's a so-called "shallow copy".

(Here I was claiming that strings are immutable in Ruby, but @BroiSatse pointed out that it's not true, and he's right, follow the link in the comments.)

Regardless, the explaination is similar: assignment of a new string into a variable produces a new reference to a string (doesn't mutate existing one), writing a new value into a hash doesn't produce a new hash reference (mutates existing hash instead).

D-side
  • 9,150
  • 3
  • 28
  • 44
2

You need to understand how ruby variables and Arrays works. Variable holds a reference to the object, which means that if you do:

a = b = []

Both a and b are referencing exactly some object!

a << 1
b    #=> [1]

Array is nothing else but an object which holds many references at the same time. But again, they just reference objects, so if you do:

a = {}
b = [a]

Then b[0] and a variable are referencing to the same object.

a[:foo] = :bar
b    #=> [{:foo => :bar}]

Now the reason why it seemed to work differently in your first example is that assignment does not modify the object, but rather changes the reference itself.

a = []
b = '1'
a.push(b)

Now both a[0] and b are pointing to the very same object. However if you do:

b = '2'

You create a new string and changes the reference for variable b, but a[0] is still referencing the old string. However, if instead of changeng the reference you execute some modifying method:

b.replace('2')

You'll see it will give you:

a   #=> ['2']
BroiSatse
  • 44,031
  • 8
  • 61
  • 86
  • So why this does not apply to the string? – meso_2600 Sep 28 '14 at 21:27
  • It does, but you did not modify string, you've created a new string and assign it to the first variable. It does not modify the second variable, which still points to the previous string. Try using some string modifying methods, like `gsub!` or `replace` to see it applies there as well. I beleive it is explained in the answer, I'll try to reword it a little. – BroiSatse Sep 28 '14 at 21:28
  • This one is good :) irb(main):012:0> a=[] => [] irb(main):013:0> b=[] => [] irb(main):014:0> b.push("1") => ["1"] irb(main):015:0> a.push(b) => [["1"]] irb(main):016:0> b.push(a) => ["1", [[...]]] irb(main):017:0> a => [["1", [...]]] irb(main):018:0> b => ["1", [[...]]] – meso_2600 Sep 28 '14 at 21:29
  • 1
    @meso_2600 - You can even do `a = []; a.push(a)`. – BroiSatse Sep 28 '14 at 21:43