12

Considering that in the Ruby programming language everything is said to be an Object, I safely assumed that passing arguments to methods are done by reference. However this little example below puzzles me:

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil

As you can see the original Object wasn't modified, I wish to know why, and also, how could I accomplish the desired behavior ie. Getting the method to actually change the object referenced by its argument.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
jlstr
  • 2,986
  • 6
  • 43
  • 60
  • I don't think that parameter passing has anything to do with object-orientation. It is orthogonal to that. And if you would like to discuss it, the difference between inout and in parameters e.g. in Corba is more appropriate. – mliebelt Oct 07 '11 at 14:27

5 Answers5

24

The way Ruby works is a combination of pass by value and pass by reference. In fact, Ruby uses pass by value with references.

You can read more in the following threads:

Some notable quotes:

Absolutely right: Ruby uses pass by value - with references.

irb(main):004:0> def foo(x) x = 10 end
=> nil
irb(main):005:0> def bar; x = 20; foo(x); x end
=> nil
irb(main):006:0> bar
=> 20
irb(main):007:0>

There is no standard way (i.e. other than involving eval and metaprogramming magic) to make a variable in a calling scope point to another object. And, btw, this is independent of the object that the variable refers to. Immediate objects in Ruby seamlessly integrate with the rest (different like POD's in Java for example) and from a Ruby language perspective you don't see any difference (other than performance maybe). This is one of the reasons why Ruby is so elegant.

and

When you pass an argument into a method, you are passing a variable that points to a reference. In a way, it's a combination of pass by value and pass by reference. What I mean is, you pass the value of the variable in to the method, however the value of the variable is always a reference to an object.

The difference between:

def my_method( a )
  a.gsub!( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'ruby is awesome'

and:

def your_method( a )
  a = a.gsub( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'foo is awesome'

is that in #my_method, you are calling #gsub! which changes the object (a) in place. Since the 'str' variable (outside the method scope) and the 'a' variable (inside the method scope) both have a "value" that is a reference to the same object, the change to that object is reflected in the 'str' variable after the method is called. In #your_method, you call #gsub which does not modify the original object. Instead it creates a new instance of String that contains the modifications. When you assign that object to the 'a' variable, you are changing the value of 'a' to be a reference to that new String instance. However, the value of 'str' still contains a reference to the original (unmodified) string object.

Whether a method changes the reference or the referenced object depends on the class type and method implementation.

string = "hello"

def changer(str)
  str = "hi"
end

changer(string)
puts string
# => "hello"

string is not changed because the assignment on strings replaces the reference, not the referenced value. I you want to modify the string in place, you need to use String#replace.

string = "hello"

def changer(str)
  str.replace "hi"
end

changer(string)
puts string
# => "hi"

String is a common case where the most part of operations works on clones, not on the self instance. For this reason, several methods have a bang version that executes the same operation in place.

str1 = "hello"
str2 = "hello"

str1.gsub("h", "H")
str2.gsub!("h", "H")

puts str1
# => "hello"
puts str2
# => "Hello"

Finally, to answer your original question, you cannot change a String. You can only assign a new value to it or wrap the string into a different mutable object and replace the internal reference.

$wrapper = Struct.new(:string).new
$wrapper.string = "String"

def changer(w)
  w.string = 1
end

changer($wrapper)

puts $wrapper.string
# => 1
Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
  • Great explanation. Long but definitely very worthwhile. Thanks for the code sample for the wrapper, since you've provided it, I will award yours as the accepted answer. Good job, can't thank you enough Simone. great lesson. – jlstr Oct 07 '11 at 14:58
  • 2
    There's absolutely no combination of pass-by-value and pass-by-reference, Ruby **only** uses pass-by-value (just like Java, C, etc.). The term 'pass-by-reference' has *nothing* to do with object references. Also see [this SO question](http://stackoverflow.com/questions/2027/pass-by-reference-or-pass-by-value) – molf Oct 07 '11 at 15:52
17

Assignment does not bind values to objects, it binds object references to identifiers. Argument passing works the same way.

When you enter the body of the function, the world looks like this:

 +---+                  +----------+
 | s |----------------->| "String" |
 +---+                  +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+

The code

 s = 1

makes the world look like

 +---+       +---+      +----------+
 | s |------>| 1 |      | "String" |
 +---+       +---+      +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+

The assignment syntax manipulates variables, not objects.

Like many similar languages (Java, C#, Python) ruby is pass-by-value, where the values are most often references.

To manipulate the string object, you can use a method on the string, such as s.upcase!. This sort of thing will be reflected outside of the method as it manipulate the object itself.

Logan Capaldo
  • 39,555
  • 5
  • 63
  • 78
  • Thank you. Visual aids are always great, I think I understand this part now. But for argument's sake... how could I make the method modify the referenced object("String")? – jlstr Oct 07 '11 at 14:28
2

Because both $string and s are references to the same object, the string "String". However, when you assign s to 1, you don't change the object "String", you make it reference a new object.

Lindydancer
  • 25,428
  • 4
  • 49
  • 68
  • ok, What you describe sounds a lot like passing by value, am I correct? On the other hand, How could I make the method modify the original object("String") for real? – jlstr Oct 07 '11 at 14:22
  • @user no. Passing by Value would be to duplicate the string. Here the reference is duplicated, so it's passing a reference by value ... – mb14 Oct 07 '11 at 14:25
  • 1
    You can modify the string with destructive operations, but you can never make it into an object of another type. – Lindydancer Oct 07 '11 at 14:58
1

Ruby passes values around to functions, and these values are references to objects. In your function you are reassigning s to another value, in this case a reference to 1. It does not modify the original object.

Your method isn't changing the object passed in, you're changing what s refers to.

wkl
  • 77,184
  • 16
  • 165
  • 176
  • "Ruby doesn't have any concept of passing a value around." says here: http://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value. I'm beginning to believe Ruby is pass-by-value, Could you illustrate how to accomplish referenced-object modification from within the method? – jlstr Oct 07 '11 at 14:24
  • @user766388: Yes, Ruby is *pass-by-value*. No ifs. No buts. No exceptions. If you want to know whether Ruby (or any other language) is *pass-by-reference* or *pass-by-value*, just try it out: `def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"`. – Jörg W Mittag Apr 26 '12 at 09:25
1

Actually, most managed programming languages like java , c#... almsot all do not pass by reference...

They all pass the reference by value ... that means they make another reference that point to the same object ... assigning it with a new value won't change the value of the original reference ... just what s points to ...

Besides, strings are immutable in most langs, meaning that you cannot change the value after being created.. they have to be recreated as new ones... so you will never see any change in the actual string ...

AhHatem
  • 1,415
  • 3
  • 14
  • 23
  • About how to modify the referenced object, you cannot modify a string in most managed programming langs... you can modify it however by wrapping it in something else like a class, list, map ..or anything so you change the value of a property inside the referenced object not the reference itself – AhHatem Oct 07 '11 at 14:30
  • I don't think talking about String immutability is proper here, basically because I have tried to do the same with my own classes, ie. Changing a "Cat" object for a "Dog" object inside the method, and the result was the same (the method wouldn't change it). – jlstr Oct 07 '11 at 14:35
  • What I meant was changing something inside the cat object... not totally changing cat to something else... that won't work for the reasons explained above... if you want to pass cat to a function and change it to dog.. you will have to wrap it inside some other object. – AhHatem Oct 07 '11 at 14:40
  • What I mean with String immutability is that directly changing a string always creates a new string in most langs, you cannot modify the value of the second character in place for example. you have to create a new string with the new value... I said this because you asked how to change the actual string... so the answer was that strings do not change. – AhHatem Oct 07 '11 at 14:46
  • actually Strings are mutable in Ruby – newacct Oct 07 '11 at 20:13