2

After reading Is ruby pass by reference or value? I have learned a lot, but I am left with more questions than I had before reading it (which I suppose is good).

Consider the following example

def foo(bar) 
  bar = 'reference' 
end
baz = 'value' 
foo(baz)
puts "Ruby is pass-by-#{baz}"

Output Ruby is pass-by-value

Here is my attempt to dissect how this works:

First, in the global scope baz has the value value.

Now foo takes a parameter, whatever you pass into it, is on a local level.

Therefore when we pass baz in, there is ANOTHER baz that is equal to reference but this is on the local level, as a result, when we puts this on a global level it prints value.

Now consider another example

def foo(bar)
  bar.replace 'reference' 
end
baz = 'value'
foo(baz)
puts "Ruby is pass-by-#{baz}"

Output

Ruby is pass-by-reference

If what I said above is true, does the .replace method here change the global baz? Am I interpreting this correctly? Please feel free to point out any mistakes in my attempts, I have no clue if im on the right track.

Thanks!

EDIT

More Magic

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"
Community
  • 1
  • 1
bill
  • 378
  • 2
  • 9

5 Answers5

1

Ruby is pass-by-value, but the values are references to objects.

In your first experiment, baz is a reference to the string "value". bar is initialized to a copy of baz (that is, a copy of the reference) when you call foo. You then overwrite bar with a reference to the string "reference". Since bar is a copy, overwriting it doesn't change baz.

In your second experiment, again, baz is a reference to the string "value" and bar is initialized to a copy of baz when you call foo. This time you don't overwrite bar, but call a method on it. Although bar is a copy of baz, they refer to the same object (the string "value"). Calling the method changes the state of that object. You then call to_s on baz (indirectly, by substituting it into "Ruby is pass-by-#{baz}"), and to_s returns the new state.

Your third experiment is a lot like the second. In the method, you change the state of the object referred to by the copy of the reference, then, outside the method, you read the new state back through the original reference.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
  • You totally lost me on the second experiment part.. my brain doesent get it -__- – bill Aug 31 '16 at 13:44
  • `bar` is not initialised to a copy of `baz` when you call `foo`. It refers to the same object in this first moment. – Fran Martinez Aug 31 '16 at 13:47
  • @bill I reworded. If that's not clearer, let me know what part isn't clear. – Dave Schweisguth Aug 31 '16 at 13:51
  • @FranMartinez it is initialized to a copy of the _reference_. – Dave Schweisguth Aug 31 '16 at 13:52
  • OoOoOoOoOoOoOo Makes sense.. Is the first example the same logic for my newly edited example? – bill Aug 31 '16 at 14:00
  • Actually it's the second and third examples that are almost the same. I added that to my answer. – Dave Schweisguth Aug 31 '16 at 14:05
  • But I thought the third example is the same as the first, we use `=` since `a_hash` is a copy it doesent change the original `hash` – bill Aug 31 '16 at 14:19
  • In the third example you copy `hash` (which a reference to an object) to `a_hash` (which is a copy, but refers to the same object). You then call a method (`[]=`) on that object which changes its state. That's much like example 2, where you called `replace` on the string. – Dave Schweisguth Aug 31 '16 at 14:25
  • I never knew calling `[]` is calling a method... so basically almost in all cases we will get `reference`?. The only time we really get value is when directly setting the method variable to something else (`bar = reference`) – bill Aug 31 '16 at 14:34
  • That is, in the first example, the method gets a copy of the reference but you don't do anything to it before you overwrite it, so the object that `baz` points to is unchanged. – Dave Schweisguth Aug 31 '16 at 14:43
  • Hi, I asked a new question here http://stackoverflow.com/questions/39274077/what-is-the-usage-of-self-in-this-ruby-example. If you have time you can check it out – bill Sep 01 '16 at 14:21
1

Very interesting thing.

Play with the object_ids, you will see what ruby is doing bellow the scenes:

def foo(bar)
  puts bar.object_id
  bar = 'reference'
  puts bar.object_id
end

baz = 'value'
puts baz.object_id
foo(baz)

Output

> baz = 'value'
=> "value"

> puts baz.object_id
70241392845040

> foo(baz)
70241392845040
70241392866940

After the local assign bar = 'reference', the local variable bar will reference another object, so it won't change the original one.

It seems that in some cases it will make a dup of your object.

Fran Martinez
  • 2,994
  • 2
  • 26
  • 38
  • it seems .`replace` doesent* change the `object_id`, why is that? Also I added another example wit hashes if you would like to take a look at that. – bill Aug 31 '16 at 13:48
1

Maybe this will help to understand it:

x = 'ab'
x.object_id
=> 70287848748000 # a place in memory

x = 'cd'
x.object_id
=> 70287848695760 # other place in memory (other object)

x.replace('xy')
x.object_id
=> 70287848695760 # the same place in memory (the same object)
maicher
  • 2,625
  • 2
  • 16
  • 27
0

It actually has nothing to do with passing parameters to methods. I extracted important parts from your examples:

baz = 'value'
bar = baz
bar = 'reference'
puts baz
bar = baz
bar.replace 'reference'
puts baz

You may think of variables as pointers. When you use =, you make a variable point to something different, and the original value remains unchanged, and can be accessed through other variables that point at it. But when you use replace, you change the content of the string the variable points at.

kxmh42
  • 3,121
  • 1
  • 25
  • 15
  • Ok gonna take a look at this and other answers, I added another magic example by the way – bill Aug 31 '16 at 13:46
0

In first case you use bar = 'reference' which creates new object. In second one .replace changes the object you apply it to. You can ensure this by .object_id method. For example:

def foo_eql(bar)
  bar = 'reference'
  puts bar.object_id
  bar
end

def foo_replace(bar)
  bar.replace 'reference'
  puts bar.object_id
  bar
end

baz = 'value'
puts baz.object_id #Here you will get original baz object_id
res1 = foo_eql(baz) #Here you will get printed new object_id
res2 = foo_replace(baz) #Here you will get printed original baz object_id
puts "foo_eql: Ruby is pass-by-#{res1}"
=> foo_eql: Ruby is pass-by-reference
puts "foo_replace: Ruby is pass-by-#{res2}"
=> foo_replace: Ruby is pass-by-reference

So there is no magic at all. In your example with hashes you do not create new Hash object but modify existing one. But you can create new one with method like this:

def my_foo(a_hash)
  a_hash = a_hash.merge({"test" => "reference"})
end
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"

Basically you pass the reference to an object in "value" way. For better understanding check this post and comments to it.

Community
  • 1
  • 1
Maxim Pontyushenko
  • 2,983
  • 2
  • 25
  • 36