Demo: Demo: Working with References

Debugging a reference-related issue

Scroll down...

Content

Resources

Comments

Understanding that objects are really just references to a place in memory (as discussed in the Pass by Reference lesson) is a major step towards understanding how Ruby (and programming languages in general) works.

In this brief demo, we'll look at a reference-related "bug" that comes up when we're building a simple method and how to handle it.

Buggy Array Doubler

Let's say I want to build a simple function which takes an array and doubles each input to it:

def array_doubler(arr)
  new_arr = []

  arr.each do |item|
    new_arr << item * 2
  end

  new_arr
end

arr = [1,2,3,4]
array_doubler(arr)
#=> [2,4,6,8]

Okay, let's say I decided to make things a bit more efficient by using map instead of each since it returns a new array anyway, only I forgot to remove the shovel operator (oops):

def array_doubler(arr)
  new_arr = []            # oops, not needed
  arr.map do |item|
    new_arr << item * 2   # oops, not needed
  end
end

arr = [1,2,3,4]
array_doubler(arr)
#=> [[2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8]] 

Now THAT was unexpected! Why did we get an array filled with arrays, particularly ones that are all the same!?

Let's play with it:

arr = [1,2,3,4]
result = array_doubler(arr)
#=> [[2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8]] 

# What happens if we change one of the sub-arrays?
result[0][0] = 100
#=> [[100, 4, 6, 8], [100, 4, 6, 8], [100, 4, 6, 8], [100, 4, 6, 8]] 

#... They all changed!

You should be suspicious now that all your sub-arrays are actually just references to the same array in memory, so changing one is actually changing them all (since if you change the original array in memory, all the pointers to it will now be pointing to the updated array as well).

Let's verify:

arr = [1,2,3,4]
result = array_doubler(arr)
#=> [[2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8], [2, 4, 6, 8]] 

result[0].object_id
#=> 70319112430920
result[1].object_id
#=> 70319112430920     ...same
result[2].object_id
#=> 70319112430920     ...same
result[3].object_id
#=> 70319112430920     ...same

Okay, so clearly we have a problem. Why did this happen?

It's because map returns the last line that was executed (implicit return) of the block you pass it. In this case, that line is:

new_arr << item * 2

When you use the shovel operator <<, you actually return the original array. In this case, we're returning new_arr implicitly with each iteration of the block. As you know, arrays are actually just references to a place in memory so each iteration through the loop is returning the same reference to the same array in memory.

In reality, your map function is effectively returning:

[new_array, new_array, new_array, new_array]

So it should make sense now why they're all the same and why changing one changes them all.

But why does the new_array contain those particular values?

Because each iteration through map you are shoveling a new value into new_array. This means that, after the first iteration, new_array will equal simply [2].

After the second iteration, it will equal [2,4].

After the third, [2,4,6].

After the fourth, [2,4,6,8].

And, as we saw before, that means they ALL end up as [2,4,6,8]

Fixing it Up

This "bug" is simple to fix by not using new_array at all and relying on map to return a new array populated with the implicit returns of each iteration through its block:

def array_doubler(arr)
  arr.map{ |item| item * 2 }
end

Final Point of Interest: << vs +

We got into trouble in part because the shovel operator modifies and returns the original array. The addition operator, +, will typically create and return a copy instead.

Let's start by showing the shovel operator working on the original array:

arr = [1,2,3]
arr.object_id
#=> 70319112367580

# Shoveling doesn't give us a new array:
shovel_arr = arr << 4
#=> [1,2,3,4]
shovel_arr.object_id 
#=> 70319112367580
arr
#=> [1,2,3,4]

# ...so we know that modifying the original modifies both:
arr.pop
#=> 4
arr
#=> [1,2,3]
shovel_arr
#=> [1,2,3]

Okay, so what happens with simple addition using +?

arr = [1,2,3]
arr.object_id
#=> 70319112158740

assigned_arr = arr + [4]
#=> [1,2,3,4]

assigned_arr.object_id
#=> 70319112136580      #...different!

Keep that in mind in the future!



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!

Next Lesson: Best Practices for Working With Methods