Learning Mutation in Ruby

I am always amazed when I realize the disconnect between understanding and my perceived understanding of a concept. I find that the moments when I realize just how large the gap is between these two levels are the moments when I improve the most. One such instance of this was on the concept of mutation in Ruby while I was working through the 101 Programming Foundations course at Launch School.

In theory, mutation in the Ruby language looks simple. Just about everything is Ruby is an object and therefore has it’s own identity. Variables, however, are references to these objects. Variables are pointer to physical space in memory. This leads to the question: when a variable is reassigned will it point to a newly created object or will the object to which it is pointing be altered (mutated)? The answer: sometimes one, sometimes the other. If you call a method that mutates the caller then it will change the value of the physical address. The variable will point to the same location, but that location has changed value. This can cause some undesirable effects. For example, take the following code:

Ex: a method that will mutate the values of an array

Output:

array1: ["MY", "NAME", "IS", "ALEX."]
array2: ["MY", "NAME", "IS", "ALEX."]

But wait a second… didn’t the #upcase! method act on array1 and not array2? This is an example of a method that mutates that caller. On line 2, array2 is assigned to a new, empty array which means array1 and array2 point to different objects. So far so good. On line 3 each value from array1 is pushed onto array2. The << method does mutate the caller therefore array2 is permanently altered to look exactly like array1. Line 4 then focuses on array1 and modifies its values using the #upcase! method. If we are modifying array1 then why did array2 get modified as well?

To answer that we must understand what the arrays and their values are actually pointing to in physical memory. Remember that variables are pointers to locations, just references to the actual information. When array2 was assigned the values from array1, what was really passed into array2 was the pointers to the values, not the values themselves. In turn, when the method #each was called on array1 and mutated its values, the objects in physical memory were changed and, since array2 has values that point to those same locations in memory, array2 was altered as well.

In a case such as this, the likely intention is not to mutate array2, only array1. Let’s now look at a similar example that will do just that.

Ex: a method that will not mutate the values of an array

Output:

array1: ["MY", "NAME", "IS", "ALEX."]
array2: ["my", "name", "is", "alex."]

To achieve this, line 4 was altered so that array1 itself is now the caller being mutated instead of the values inside of it. On this line value.upcase does not mutate the value but instead only returns it with all-caps. #map! is then used to take this collection of all-caps values and assign it to array1. Since it is the array1 itself that is being mutated, array2 remains untouched.

There is no doubt you will run into issues such as in programming. The takeaway from this to be mindful of what you are truly mutating. Is it the array or the values inside the array? Being able to identify the difference will save you time and frustration as you learn to code.