To understand mutation in arrays, first we need to review Variables as Pointers and especially the Mutability section.
When applied to Arrays (or collections), mutability can have be the source of headaches. We will use Array#object_id
to see what is going on under the hood.
# Initialize a first Array object and dup it to initialize a second one
array1 = ["一", "二", "三"]
array2 = array1.dup
# Their object ID are different. Nothing special here
array1.object_id # => 240
array2.object_id # => 260
# The object ID of both array's first element are the same
array1[0].object_id # => 280
array2[0].object_id # => 280
# Let's mutate the second element of array1
array1[1] << '円' # => "二円"
# Both array's second element are updated
puts array1[1] # => 二円
puts array2[1] # => 二円
# They still have the same object ID
array1[1].object_id # => 320
array2[1].object_id # => 320
# They are still two different array, though
array1.object_id # => 240
array2.object_id # => 260
Instead of Array#dup
we could have use a simple assignement. The results would have been the same except the object ID of array1
and array2
would have been the same.
What happened is that while both arrays are different object thanks to the Array#dup
method, their elements points to the same address in memory. Modifying an element in one array will “affect” the same element in the other array.
Let’s imagine a scenario where you initialize a local variable to an element of an array, in order to use later in the code. However, before using the array you mutate the element in the array:
animals = ['flamingo', 'red panda']
fancy_bird = animals[0]
animals[0].clear
# Do a bunch of things here...
puts "The #{fancy_bird} is pink!" # => The is pink!
While the code above is silly, it gets more confusing when introducing methods to the mix because of Ruby’s Pass by Reference of the Value.
For example, given the following methods’ implementation, will the returned object be the same object as the one passed in as an argument or a different object?
# Method to reverse the letters in a string
def spin_me(str)
str.split.each(&:reverse!).join(" ")
end
str = 'hello world'
puts str.object_id
puts spin_me(str).object_id
# Method to reverse the letters in strings contained in an array
def spin_me(arr)
arr.each(&:reverse!)
end
arr = ['hello', 'world']
puts arr.object_id
puts spin_me(arr).object_id
(The answer is different object for the first method and same object for the second.)