What does it mean for an object to be mutable?

Don’t let the fancy word confuse you, it just means that the object state can be changed.

Not all objects in Ruby are mutable.

It doesn’t make any sense for numbers or symbols, or even true or false (which are also objects) to change.

But other objects, especially those that are meant to store data, like Array or Hash objects, make a lot more sense because it would not be very efficient otherwise.

What’s the alternative?

You can make a new copy of an object with the changes & then return this new object, leaving the original object intact.

If arrays were immutable and you wanted to change just one element of an array, you would have to copy all the data, including the elements that didn’t change.

Imagine having to copy a one million (or more) element array every time you had to make any change, doesn’t matter how small! Not very efficient…

Anyway.

Let’s look a little deeper into how mutability works in Ruby.

Mutability & Variables as Pointers

There is a category of programming errors which are caused by the combination of two things: mutable objects & the fact that variables don’t contain data directly, but a reference to where this data is stored.

One way these errors show themselves is when you try to ‘alias’ a variable.

Here is an example:

name = "Peter" other_name = name puts other_name # "Peter"

In this example, both name and other_name contain a reference to the same string object. You can use either to display or modify the contents of this string.

The problem appears if we treat other_name like a copy of the string.

other_name[0] = 'T' name # "Teter" other_name # "Teter"

Since both variables point to the same string, we just changed “Peter” to “Teter”. That’s a problem because we probably meant to keep “Peter” around.

Cloning Objects

One way to deal with this issue is to use the dup method.

This will tell Ruby to give you a copy of the object. There is also a clone method, which in addition to giving you a copy of the object, it copies the frozen status & any singleton methods defined on the object.

Let’s see an example:

numbers = [1, 2, 3] more_numbers = numbers.dup more_numbers << 4 numbers # [1, 2, 3] more_numbers # [1, 2, 3, 4]

In this example, you can see how the original numbers array remained unchanged. Try removing that dup call on the third line and see what happens 🙂

The Ruby Freeze Method

Another way to keep an object safe from unwanted changes it to 'freeze' it. Any Ruby object can be frozen by using the freeze method.

When an object is frozen, any attempt to change this object will result in a RuntimeError exception.

Note: You can use the frozen? method to check if an object is frozen or not.

Example:

animals = %w( cat dog tiger ) animals.freeze animals << 'monkey' # RuntimeError: can't modify frozen Array

One thing to keep in mind is that this will only freeze one object, in this example the array itself, which prevents us from adding or taking away items from it. But the strings inside the array are not frozen, so they can still be changed!

animals[1][0] = 't' # => ["cat", "tog", "tiger"]

If you want to freeze the strings you need to call freeze on them. Like this: animals.each(&:freeze) .

Frozen Strings

Mutable objects also have an impact on performance, especially strings. The reason is that there is a good chance that in a large program the same string is used multiple times.

Ruby will create a new object for every string, even if two strings look the same, or in other words, they have the same 'content'. You can easily see this happen in irb if you use the object_id method.

Here is an example:

a = 'test' b = 'test' a.object_id # 76325640 b.object_id # 76317550

This is a problem because these objects are consuming extra memory and extra CPU cycles.

Starting with Ruby 2.1, when you use frozen strings, Ruby will use the same string object. This avoids having to create new copies of the same string. Which results in some memory savings and a small performance boost.

Rails makes extensive use of frozen strings for this reason. For example, take a look at this PR.

This prompted the Ruby development team to start considering moving strings into immutable objects by default. In fact, Ruby 2.3, which was just released a few days ago, includes two ways to enable this for your project.

One is to include # frozen_string_literal: true at the top of every file where you want strings to be immutable. And the other is to use a command-line argument --enable=frozen-string-literal .

Immutable strings by default will probably land in Ruby 3.0.

Now don't go crazy and start freezing all your strings in your app. You only want to do this for strings that are used hundreds of times to see some sort of benefit. Having said that, here is a tool that you can use to find potential strings to freeze.

Know Your Methods

Not all the methods in a mutable object will actually change the object, for example, the gsub method will return a new string, leaving the original untouched.

Some of these methods have an alternative version which does change the original object in-place, which is often more efficient. These methods often end with an exclamation symbol ! to indicate their effect.

Two examples of these 'bang' methods are gsub! and map! .

Note this:

A method ending in ! doesn't always mean that it's a 'method that changes an object'.

In more general terms, the ! symbol is used to denote 'danger'. One example of this is the exit! method, which will exit the program immediately, ignoring any exit handlers.

There is also methods that change the object and don't end with a ! symbol. For example: delete , clear , push , concat , and many more.

Wrapping Up

Mutability can be a tricky subject, but since you read this post you are now much better prepared to deal with it. Check the Ruby documentation if you aren't sure what a method is doing, this will help you avoid issues.

I hope you found this article informative, please share it with your friends so they can enjoy it too. Also join my newsletter below so you don't miss more content like this when it comes out!