0

I'm not sure what happens when you pass an object to a module method. Does the object gets passed by reference or by copy? Like in this example:

module SampleModule
  def self.testing(o)
    o.test
  end
end

class SampleClass
  def initialize(a)
    @a = a
  end

  def test
    @a = @a + 1
  end
end

sample_object = SampleClass.new(2)
3.times do
  SampleModule.testing(sample_object)
end
p sample_object # => #<SampleClass:somehexvalue @a=5>

seems to be pass-by reference. Really unclear about this.

daremkd
  • 8,244
  • 6
  • 40
  • 66

3 Answers3

2

All variables in Ruby are references to objects. You cannot "pass by value" versus "pass by reference" in the same way as you have that choice in C, C++ or Perl. Ruby in fact forces pass by value, there are no options to do otherwise. However, the values that are sent are always references to objects. It's a bit like using C or C++ where all member variables are pointers, or using Perl where you must work with references at all times, even when working with simple scalars.

I think that it is this separation of variable from object data that is confusing you.

A few points:

  • Variable allocation never over-writes other variables that may point to the same object. This is pretty much the definition of pass-by-value. However this isn't meeting your expectations that object contents are also protected.

  • Instance variables, and items in containers (e.g. in Arrays and Strings) are separate variables, and if you send a container you can alter its content directly, because you sent the reference to the container, and that includes the same variables for its contents. I think this is what you mean by "seems to be pass-by reference"

  • Some classes - including those representing numbers, and Symbol - are immutable i.e. there are no change-in-place methods for the number 4. But conceptually you are still passing a reference to the singular object 4 into a routine (under the hood, for efficiency Ruby will have the value 4 encoded simply in the variable's memory allocation, but that is an implementation detail - the value is also the "pointer" in this case).

  • The simplest way to get close to the "pass by value" semantics you seem to be looking for with SampleModule is to clone the parameters at the start of the routine. Note this does not actually cause Ruby to change calling semantics, just that in this case from the outside of the method you get the safe assumption (whatever happens to the param inside the method stays inside the method) that you seem to want:


module SampleModule
  def self.testing(o)
    o = o.clone
    o.test
  end
end
  • Technically this should be a deep clone to be generic, but that wouldn't be required to make your example work close to a pass-by-value. You could call SampleModule.testing( any_var_or_expression ) and know that whatever any_var_or_expression is in the rest of your code, the associated object will not have been changed.
Neil Slater
  • 26,512
  • 6
  • 76
  • 94
  • Maybe you can clarify that `o.clone` does not _pass by value_, but _creates a new object copying the cloned object instance variables and returns it_; this implies that the instance variables of the new object reference to the respective instance variables of the cloned object, which is a crucial difference from "pass by value" concept – mdesantis Nov 09 '13 at 12:26
  • 1
    @ProgNOMmers - Yes, I think I meant to use "deep clone", and my example works only because the attribute is numeric. With a deep clone, you get closer to behaviour of "pass by value" in principle (as in anything you do with the param inside the method is unimportant to the caller), although really when you have a deep tree of references, it's not such a clean concept in the first place. – Neil Slater Nov 09 '13 at 17:06
  • This is a thing I miss in Ruby: there is not a clear way to do a deep dup of objects; in most cases you have to implement it by yourself. – mdesantis Nov 10 '13 at 01:54
  • 1
    Ruby is pass-by-value. No need to "quote" it. Always. No exceptions. No ifs. No buts. You can check that out yourself if you don't believe me: `def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}" # Ruby is pass-by-value` – Jörg W Mittag Nov 10 '13 at 03:16
  • @Jörg W Mittag: If you start with *all variables are references* then yes of course all use of them as params to methods sends the value of the pointer, and all *assignments* to a named variable affect the variable, not the object. What you cannot do in Ruby is the equivalent of C's `int x=5; foo( &x );` or Perl's `my $x=5; foo( \$x );` - passing a reference to the contents of a *variable* (although you could send the name and binding). I will try to re-word. – Neil Slater Nov 10 '13 at 07:54
  • @Jörg W Mittag: After re-wording I realise I was taking the OP's definition of pass-by-value and quoting that, as it doesn't exist as a thing in Ruby. Some languages will copy instance data for you when you make a call using pass-by-value semantics, and I think the OP was including that in his definition. – Neil Slater Nov 10 '13 at 08:24
  • So basically, any module can modify the actual object that gets passed as long as a reference to that object is passed? I'm not sure how Perl or C++ defines 'pass by reference' and 'pass by value', but I've tried to use the definition in the top answer: http://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value which seems to suggest that, according to this definition, Ruby is 'pass-by-reference' hm... – daremkd Nov 10 '13 at 11:32
  • @daremarkovic: What you are passing in Ruby is pointers, which are *copied* by the method handling mechanism. It's that copying stage that technically counts as "pass by value". For the behaviour you expected in the question, it may be splitting hairs to define it this way, but is technically more accurate description. Often we conflate variables with the object data they point to, but this is the kind of question where it is important to be clear whether talking about a variable (label for a pointer) as opposed to an object (the actual data pointed at). – Neil Slater Nov 10 '13 at 11:56
  • @NeilSlater Say you have this code, https://gist.github.com/dare05/7398661. Now, if I got this right, it goes like this: The reference variable 'bar' = a pointer to a string object gets passed by COPY to the is_ruby_pass_by_value? method. So now we have 2 reference variables (although with different scope), both pointing to the same method "bar" and the local variable "foo". Correct me if I'm wrong. So 2 references, 1 object which both references can do whatever they want with it. – daremkd Nov 10 '13 at 14:06
  • 1
    Yes. An important addition is what the *assignment* operator `=` does. It is one of the few things in Ruby that alters a variable. So when you add `foo = 'No, Ruby is pass-by-reference.'`, you create a new `String` object and point `foo` at it - at that point you have two references, each pointing to a separate object. *However* change that line to `foo.replace('No, Ruby is pass-by-reference.')` which changes the object - not the variable - and you get the opposite behaviour matching your OP. – Neil Slater Nov 10 '13 at 14:14
2

If you really want to be anal on vocabulary, Ruby passes references to (mutable) objects by value:

def pass_it(obj)
  obj = 'by reference'
end

def mutate_it(obj)
  obj << ' mutated'
end

str = 'by value'

pass_it(str)
mutate_it(str)

puts str # by value mutated

You can work around issues that may arise from this by using dup or clone (note that both do shallow copies) and freeze.

Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
-1

Everything in Ruby is passed by reference:

class Test
  attr_reader :a
  def initialize(a)
    @a = a
  end
end

s = "foo"
o = Test.new(s)
o.a << "bar"
o.a          #=> "foobar"
s            #=> "foobar"
o.a.equal? s #=> true

In your code, the fact that you are passing an object to a module method doesn't change anything; sample_object is already a reference to the new object SampleClass.new(2)

mdesantis
  • 8,257
  • 4
  • 31
  • 63
  • -1. This is just plain wrong. Ruby is pass-by-value. Always. No exceptions. No ifs. No buts. You can check that out yourself if you don't believe me: `def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}" # Ruby is pass-by-value` – Jörg W Mittag Nov 10 '13 at 03:15
  • @JörgWMittag , I got the joke, but downvote the answer wasn't nice... – mdesantis Nov 15 '13 at 10:53