bill
bill

Reputation: 368

Understanding passing by value vs. reference in this Ruby example

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"]}"

Upvotes: 2

Views: 75

Answers (5)

Dave Schweisguth
Dave Schweisguth

Reputation: 37617

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.

Upvotes: 1

Maxim Pontyushenko
Maxim Pontyushenko

Reputation: 3053

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.

Upvotes: 0

maicher
maicher

Reputation: 2745

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)

Upvotes: 1

Fran Martinez
Fran Martinez

Reputation: 3042

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.

Upvotes: 1

kxmh42
kxmh42

Reputation: 3830

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.

Upvotes: 0

Related Questions