Object Passing & Mutability in Ruby

Josiah Fordahl
4 min readApr 17, 2021

Variable Assignment and Variable References in Ruby:
In ruby our assignment operator = allows us to point variables at objects.
Let us jump into some code and explore this further.

Below we will assign two variables to a single string object and see how this works.

acknowledgement = "Hello"acknowledgement.object_id #=> 100------------------------------------------salutations = acknowledgementsalutations.object_id #=> 100 

Here we have two different variables pointing at (also called referencing) one single object.

First we assign acknowledgement to the String object “Hello”.

Then we assign salutations to point at the String object that acknowledgement points to.

In the above image, “Referencing” and “Points at” are two terms for the same thing. When we assign an object to our variable, we are creating a pointer from our variable to our object.

Variable Reassignment, Mutation, and Non Mutating Methods in Ruby:

acknowledgement = "Goodbye"
acknowledgement.object_id #=> 120
salutations #=> "Hello"
salutations.object_id #=> 100

In the above code we have reassigned acknowledgement to reference a new String object “Goodbye”.

Our two variables now reference two different string objects. This has occurred through the use of “reassignment”.

Objects in Ruby can either be mutable or immutable. Immutable means it cannot be modified. Conversely, mutable means the object can be modified.

For example, numbers and booleans are immutable, while strings are mutable in Ruby. There are no methods available within Ruby that allow you to mutate an immutable object.

1.) int = 102.) p int #=> 10 3.) int = 10 * int4.) p int #=> 100

Wait, doesn’t line 4 show that we just mutated an immutable object?

Most definitely not.

  1. Here we assign variable int a value of 10.
  2. We pass 10 (the object referenced by local variable int ) to p as an argument, this shows us that the assignment from line 1 has been carried out successfully.
  3. Here we reassign int to the return value of 10 * int .
  4. We pass the object that int points to as argument to p, which outputs 100 , this shows us that the reassignment from line 3 has been carried out successfully.

In the above example we have reassigned the variable to point at a different object. We have not mutated the original object.

Assignment simply tells Ruby to bind an object to a variable. It does not mutate the object. When using = (assignment) remember you are not mutating your object, you are pointing at new objects.

Add AND Assignment Operator +=
This is not a method, this is an assignment operator.
It adds the right operands and left operands and assigns the result to the left.
Take a look at the example below:

a = 10 
b = 12
a += bp a #=> 22
p b #=> 12

In the above example a += b is the same as saying…

a = a + b

We are adding the current value of the left side to the right side and saving the result to the left side.

Let’s dig deeper:

What do you think happens with the below code snippet?

def non_mutation(str)
str += '!!'
str.upcase
end

annoyed = "go away"
angry = non_mutation(annoyed)

First, annoyed is passed to our method non_mutation . This points the method parameter str to our string object “go away”.

str and annoyed now point to the same String object “go away”.

On the second line, we have reassigned str which now points at the String object “go away!!” , a completely new object.
At this point annoyed is still pointing at String object“go away" . While str is pointing at the new object “go away!!”.

String #upcase returns a copy of str with all lowercase letters replaced with their uppercase counterparts.

One the third line, we call the string method #upcase on the object our variable str references.
Here, annoyed is still pointing at the original string object “go away” .

Now #upcase is called on str which returns “GO AWAY!!” .
As we have seen, the original String object passed in to non_mutation remains un-mutated.

On the last line, our variable angry is therefore assigned to the return value of invoking non_mutation with the string object that annoyed references, passed as an argument.

The above code snippet is non-mutative.

def mutation(str)
str << '!!'
str.upcase!
end

asking = "come here"
mutation(asking) #=> "COME HERE!!"

On the last line, we invoke mutation and pass the string object “come here” that our variable asking references. At this point, now str and asking both reference this object.

The Shovel Operator String#<<

Appends the given object to str. (This is a mutating method, which modifies the original object it is called on.)

On line 2, the shovel operator << mutates the object that both str and asking point at, which now is “come here!!” .

#upcase!

Upcases the contents of str, returning nil if no changes were made.

On line 3, we call String method#upcase! on the string object thatstr references. #upcase! is mutative and modifies the object in place, this is returned as it is the last line of the method (again implicit return value).

At this point both str (the mutation method’s parameter) and the local variable asking are pointing at the same object, that is “COME HERE!!” .
This object has been mutated in place by invoking our methodmutation and passing the local variable asking as an argument.

As you can see, this code snippet is mutative.

--

--