2

I have Ruby code:

def test_111(hash)
  n = nil
  3.times do |c|
      if n
       n[c] = c
      else
       n = hash
      end
  end
end

a = {}
test_111(a)
p a

Why it print {1=>1, 2=>2}, not the {} ??

In the test_111 method, the hash and the a use the same memory?

How can the a value be changed in the test_111 method?

I can't understand

tinylian
  • 305
  • 1
  • 2
  • 9

2 Answers2

4

Hashes are passed by reference. So, when you change a method parameter (which is a Hash), you change the original hash.

To avoid this, you should clone the hash.

test_111(a.dup)

This will create a shallow copy (that is, it will not clone child hashes that you may have).

A little illustration of what shallow copy is:

def mutate hash
  hash[:new] = 1
  hash[:existing][:value] = 2
  hash
end

h = {existing: {value: 1}}

mutate h # => {:existing=>{:value=>2}, :new=>1}
# new member added, existing member changed
h # => {:existing=>{:value=>2}, :new=>1}



h = {existing: {value: 1}}

mutate h.dup # => {:existing=>{:value=>2}, :new=>1}
# existing member changed, no new members
h # => {:existing=>{:value=>2}}
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
0

In ruby, just about every object is passed by reference. This means when you do something as simple as

a = b

unless a was one of the simple types, after this assignment a and b will point to the same thing.

This means if you alter the second variable, the first is affected the same way:

irb(main):001:0> x = "a string"
=> "a string"
irb(main):002:0> y = x
=> "a string"
irb(main):003:0> x[1,0] = "nother"
=> "nother"
irb(main):004:0> x
=> "another string"
irb(main):005:0> y
=> "another string"
irb(main):006:0> 

and of course the same applies for hashes:

irb(main):006:0> a = { :a => 1 }
=> {:a=>1}
irb(main):007:0> b = a
=> {:a=>1}
irb(main):008:0> a[:b] = 2
=> 2
irb(main):009:0> a
=> {:a=>1, :b=>2}
irb(main):010:0> b
=> {:a=>1, :b=>2}
irb(main):011:0> 

If you don't want this to happen, use .dup or .clone:

irb(main):001:0> a = "a string"
=> "a string"
irb(main):002:0> b = a.dup
=> "a string"
irb(main):003:0> a[1,0] = "nother"
=> "nother"
irb(main):004:0> a
=> "another string"
irb(main):005:0> b
=> "a string"
irb(main):006:0> 

For most people dup and clone have the same effect.

So if you write a function that modifies one of its parameters, unless you specifically want those changes to be seen by the code that calls the function, you should first dup the parameter being modified:

def test_111(hash)
  hash = hash.dup
  # etc
end

The behavior of your code is called a side effect - a change to the program's state that isn't a core part of the function. Side effects are generally to be avoided.

Michael Slade
  • 13,802
  • 2
  • 39
  • 44