Empirically, this looks to me like there is some kind of distinction between scalar objects and more complex objects such as hashes and arrays, with scalars being passed by value and complex objects by reference.
There is no such thing as a "scalar object" or a "complex object" in Ruby. Everything is an object. Period. And everything is pass-by-value, always, no exceptions. There is never any pass-by-reference going on, ever.
More precisely, Ruby is what is commonly called call-by-object-sharing, call-by-sharing, or call-by-object. This is a special case of pass-by-value, where the value being passed is always a pointer to an object.
Free variables in closures are captured by reference, but that is a different question and has nothing to do with this one.
That would be similar to the semantics of C.
No, actually, it wouldn't. There is no pass-by-reference in C, C is always pass-by-value, just like Ruby.
In C, everything is passed by value. int
s are passed by value. char
s are passed by value. And pointers are passed by value. Ruby is like C, except there are only pointers; every value that is passed is a pointer to an object.
def f(x)
x = 7
end
a = 3
f(a)
a #=> 3
def f(x)
x[0] = 7
end
a = [3]
f(a)
a[0] #=> 7
These two cases are fundamentally different: in the first case, you bind a new value to the parameter x
inside the method. This re-binding is only visible inside the method body. Method parameters essentially behave like local variables. (In fact, if you reflect upon the local variables of a method body, you will see that the parameters show up.)
In the second case, you call a method that mutates the receiver. There is no assignment going on. Yes, there is an equals sign, but that is just a part of Ruby's indexing method assignment syntactic sugar. What you are really doing, is calling the method []=
, passing 0
and 7
as arguments. It is completely equivalent to calling x.[]=(0, 7)
; in fact, you can write it that way if you want. (Try it!) Maybe, you would be less confused, if you used the insert
method instead of []=
, or another method whose name more obviously screams "I am changing the array", such as clear
or replace
?
The array is still the exact same array that you passed into the method. The reference was not modified. Only the array was. Arrays would be pretty useless if we couldn't insert stuff into them, and that stuff then stayed in there!
So, the difference between the two cases is that in the first case, you assigned a new value, i.e. you mutated the reference, which doesn't work, because Ruby is pass-by-value. In the second case, you mutated the value, which does work, because Ruby is not a purely functional language with purely immutable objects. Ruby is impure, and it does have mutable objects, and if you mutate an object, well, the object mutates.
My mom and my hairdresser refer to me by different names, but if my hairdresser cuts my hair, my mom will also observe that fact.
Note: there are objects which don't have methods that mutate them. These objects are immutable. Integer
s are such immutable objects, so you can never demonstrate something like the above with Integer
s, but that is purely a result of the fact that Integer
s don't have mutating methods, it has nothing to do with them being "scalar". You can have complex, compound objects that don't have any mutating methods, if you want: here is a question about implementing a linked list in Ruby, the two answers contain three implementations of linked lists, all of them immutable. (Disclaimer: one answer with two implementations is from me.)