Is Ruby pass-by-value or pass-by-reference? [dupli

2019-03-21 11:31发布

问题:

This question already has an answer here:

  • Is Ruby pass by reference or by value? 12 answers

I am basically a java developer. I am working in ruby for about an year. Unlike java, Ruby is a pure object oriented programming language. Here comes a doubt. Is it pass-by-value or pass-by-reference? Java works as pass-by-value: "When passing primitives, I see that the value is duplicated and passed to the method. But incase of Objects, the reference is duplicated and passed to the method. The reference contains the location of the object in the heap. During the method call, only the location of the object is passed. So no duplicate objects are created. The same object is modified."

But when I tried the below ruby code snippet, I got the same results as I got in Java: "The numbers work like a primitive (like in java) during a method call whereas the array works as perfect references like in java". Now, I am confused. If everything in ruby are objects, then why the number objects are duplicated during the method call?

class A
  def meth1(a)
    a = a+5
    puts "a inside meth1---#{a}"
  end

  def meth2(array)
    array.pop
    puts "array inside meth2---#{array}"
  end
end


obj1 = A.new
aa=5
obj1.meth1(aa)
puts "aa-----#{aa}"

arr = [3,4,5]
obj1.meth2(arr)
puts "arr---#{arr}"

Results:

a inside meth1---10

aa-----5

array inside meth2---34

arr---34

回答1:

Ruby uses pass-by-value, or more precisely, a special case of pass-by-value where the value being passed is always a pointer. This special case is also sometimes known as call-by-sharing, call-by-object-sharing or call-by-object.

It's the same convention that is used by Java (for objects), C# (by default for reference types), Smalltalk, Python, ECMAScript/JavaScript and more or less every object-oriented language ever created.

Note: on all existing Ruby implementations Symbols, Fixnums and Floats are actually passed directly by value and not with an intermediary pointer. However, since those three are immutable, there is no observable behavioral difference between pass-by-value and call-by-object-sharing in this case, so you can greatly simplify your mental model by simply treating everything as call-by-object-sharing. Just interpret these three special cases as internal compiler optimizations that you don't need to worry about.

Here's a simple example you can run to determine the argument passing convention of Ruby (or any other language, after you translate it):

def is_ruby_pass_by_value?(foo)
  foo.replace('More precisely, it is call-by-object-sharing!')
  foo = 'No, Ruby is pass-by-reference.'
  return nil
end

bar = 'Yes, of course, Ruby *is* pass-by-value!'

is_ruby_pass_by_value?(bar)

p bar
# 'More precisely, it is call-by-object-sharing!'


回答2:

It's pass-by-value in both cases, like Java. The difference is that both items in your tests are objects, whereas in Java one would have been a primitive, the other an object. But whether something is a primitive or an object has no bearing on pass-by-value vs. pass-by-reference. Pass-by-value vs. pass-by-reference has to do with what the called method can do to the variables in the calling context that are passed into it.

Let's ignore both languages and objects and just look at what pass-by-value vs. pass-by-reference actually mean. I'll use pseudo-code in a vaguely B/Java/C/C++/C#/D syntax:

Function Foo(arg) {
  arg = 6
}

declare variable a
a = 5
Foo(a)
output a

If a is passed by value, the output is 5. If a is passed by reference (a reference to the variable a is given to Foo), the output is 6, because Foo is working on a via a reference to the variable.

Note that there's a substantial difference between your two tests.

In your first test, you're assigning an entirely new value to a:

a = a + 5

You're not modifying the version of a passed into the method, you're using that value to assign a new value to a.

In your second test, you're just modifying array:

array.pop

Not, for instance:

array = ...put something entirely new in `array`...

In your test, since you're just modifying the thing that the object reference points to, not changing the reference, of course you see that modification. But if you'd actually assigned a new array to array, that change wouldn't be apparent in the calling context.



回答3:

See below, Object_id will answer your all questions:

class A
 def meth1(a)
  p a.object_id #=> 11
  a = a+5 # you passed a reference to the object `5`,but by `+` you created a new object `10`.
  p a.object_id #=> 21
 end

 def meth2(array)
  p array.object_id #=> 6919920
  array.pop
  p array.object_id #=> 6919920
 end
end


obj1 = A.new
aa=5
obj1.meth1(aa)
p aa.object_id #=> 11

arr = [3,4,5]
obj1.meth2(arr)
p arr.object_id #=> 6919920

So its true that in your code object reference is passed, by value . Note + create a new object thus reference is to 10 made locally to the method where it got changed.



回答4:

Ruby, as Java, is pass-by-value... With a catch : the "value" that is passed is (in case of objects) the pointer reference, so should any modification on the object value be done inside the method, it will be on the same object that the one in the calling context.

Now, unto your examples, you need to know that FixNum are "immediate" objects that have only a value - the reference in this case is not the pointer but the object itself (it still is an object, with methods, etc, so this is not a primitive like in Java).

In your example, you are actually reassigning a new object to your "a" pointer, which will in all case not be reflected anywhere, the same way as :

my_name = "John"
my_name = "Robert"

is actually assigning a new pointer to the reference. As there is no way to change the value of a FixNum in Ruby, this is not a case that can work.

The situation with the array is what you probably expect : you have an object, you do a modification on the object state and get the modified state back.