Let’s talk about mutability.
What does it mean for an object to be mutable? Don’t let the fancy word deceive you, it just means that the object state can be changed.
Not all objects in Ruby are mutable, for example, it doesn’t make any sense for numbers or symbols, or even
false (which are also objects) to change.
The alternative to mutating an object, but still being able to get an updated version of the object, is to make a new copy of the object with the changes and 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! That wouldn’t be very efficient, would it?
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
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 = '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.
One way to deal with this issue is to use the
dup method, that will tell Ruby to give us 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 🙂
Another way to keep an object safe from unwanted changes it to 'freeze' it. Any Ruby object can be frozen by using the
When an object is frozen, any attempt to change this object will result in a
Note: You can use the
frozen?method to check if an object is frozen or not.
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 = 't' # => ["cat", "tog", "tiger"]
If you want to freeze the strings you need to call
freeze on them. Like this:
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
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
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.
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
Also, note that 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:
concat, and many more.
Mutability can be a tricky subject, but since you read this post you are now much better prepared to deal with it. Don't hesitate to consult 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!