Ruby is pass-by-value, just like C, Java, Python, Smalltalk, ECMAScript and many others. C++ and C# are also pass-by-value by default, you have to use special annotations (&
in C++, ref
in C#), to use pass-by-reference.
The distinction is actually rather simple: if the the reference is passed, then the callee can modify it, otherwise it can't. In Ruby, the callee cannot modify the reference, ergo it is pass-by-value:
def is_ruby_pass_by_value?(foo)
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
# 'Yes, of course, Ruby *is* pass-by-value!'
As you can see, within the method is_ruby_pass_by_value?
, the reference bar
/foo
is not being passed, otherwise the modification would be visible afterwards. bar
is being passed by-value, i.e. the content of bar
(the value contained in it) is being passed and not the reference itself.
Now, what is the value that is being passed? It is not the String
object. Rather it is a pointer to that String
object. More precisely: a copy of that pointer.
Now, there are two pointers to that String
object. And that String
object is mutable! So, if I follow one pointer (foo
) and tell that String
object to change itself, and then I follow the other pointer (bar
) and ask it about its contents, then I will obviously see the changed contents. That's just the nature of shared mutable state, Ruby is not a purely functional, referentially transparent language:
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!'
In fact, in Ruby, the value being held by variables and being passed as arguments is always a pointer. That's how almost all object-oriented languages work. Barbara Liskov called this special case of pass-by-value "call-by-object-sharing", it is also sometimes called "call-by-sharing" or "call-by-object".
Note, however, that the fact that the value being passed is a pointer, is completely irrelevant. Pass-by-value vs. pass-by-reference is about how arguments are being passed, not what the argument is. C is always pass-by-value, regardless of whether you are passing an int
or a pointer. Pointers are still being passed by value. Likewise in Ruby, pointers are being passed by value. The differences between Ruby and C are a) that you can only pass pointers in Ruby, and b) that there is no special syntax indicating that you are passing a pointer.
[Note: most Ruby implementations will actually have optimizations in place for passing objects which are smaller than a pointer directly instead of passing a pointer to that object. However, they only do that for objects which are guaranteed by the language specification to be deeply immutable, so that it is impossible to observe the difference between passing a pointer to the value and passing the value directly. This is done, for example, for Fixnum
s, Symbol
s, Float
s, nil
, true
and false
.]
Here is an example in C#, that demonstrates the difference between pass-by-value (even if that value is a reference) and pass-by-reference:
class Program
{
static void IsCSharpPassByValue(string[] foo, ref string baz)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
}
static void Main(string[] args)
{
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var grault = "This string will vanish because of pass-by-reference.";
IsCSharpPassByValue(quux, ref grault);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
}
}