Background:
I discovered (albeit accidentally) that using a !
method (e.g. #sub!, #chomp!, etc.) within a user-defined function can actually change the way an object gets passed (i.e. by value or by reference) to that function (or at least it appears that way).
I know that !
methods are used to alter an object in-place, but honestly I've always thought that using them (or not) was just a matter of preference. I, for one, have always preferred to use the !
methods for the slightly briefer syntax (e.g. str.chomp!
instead of str = str.chomp
). However, the difference can actually be detrimental in some cases. Allow me to explain with the following example.
Example:
The example below defines two functions, change1
and change2
, that alter the value of str
to say, "CHANGED!" using the #sub and #sub! methods, respectively. They seemingly do the same thing, but notice that the value of orig
gets altered by change2
but not by change1
.
I guess I figured that orig
gets passed by value in both cases, and therefore the two functions act exactly the same way. But I was wrong.
# Pass by value
def change1(str)
str = str.sub(/^.*$/, 'CHANGED!') # Changes 'str' but not 'orig' (as expected)
return str
end
# Pass by reference...?
def change2(str)
str.sub!(/^.*$/, 'CHANGED!') # Changes 'str' AND 'orig' (unexpected!)
return str
end
orig = "original"
p change1(orig) #=> "CHANGED!"
p orig #=> "original"
p change2(orig) #=> "CHANGED!"
p orig #=> "CHANGED!"
Questions:
If objects are passed by value in Ruby, how does the use of a !
method such as str.sub!(...)
alter an object that's not even in the same scope (seemingly passing it by reference)?
Likewise, why does the use of a non-!
method such as str = str.sub(...)
act differently?
Most importantly, how could I have ever known that these two types of methods (seemingly) affect the way an object gets passed to my function? For example, the documentation for #sub! only states that it "Performs the same substitution as #sub in-place."