Apr 22, 2015
Table of contents:
If you’ve been following along with the posts in this series you might have been wondering what a Symbol is.
You can recognise a Ruby Symbol because it will be a word that starts with a :
.
So far we’ve seen them as the key in a Hash
(Working with Hashes in Ruby):
person = { name: 'Philip' }
And as the arguments to a Struct
(Working with Structs in Ruby):
Location = Struct.new(:longitude, :latitude)
When I first started learning Ruby, it wasn’t clear what Symbols represented or why you would use them.
Symbols in Ruby actually have a very explicit meaning and we use them for an important reason.
In today’s tutorial we will be looking at Symbols and why they are important in the Ruby language.
If you are approaching Ruby from a programming language like PHP or Javascript, the purpose of Symbols can seem unclear.
The ambiguity of Symbols is made worse as they seem to often be used interchangeably with Strings.
As we saw in Ruby Strings, the String
data type in Ruby is special because it is actually an object.
A Symbol is basically the same as a String
, but with one important difference. A Symbol is immutable.
In programming, when something is immutable it means it can’t be changed. Once you create an immutable object, it will remain exactly the same until it is destroyed.
This is very important in programming because mutable objects can lead to bugs that are hard to diagnose.
So a Symbol is essentially the same as a String, but it is immutable.
A good way of thinking about Symbols is, a Symbol is identifying something of importance, whereas a String is usually used to hold a word or a piece of text we need to work with.
To better understand Symbols in Ruby, let’s get our hands dirty and start to play around in IRB.
So the first thing we can do is to inspect a Symbol to see what class it uses:
:hello.class
# => Symbol
'hello'.class
# => String
So we can see that Symbols and Strings are instances of two different objects.
You can call String-like methods such as upcase
, downcase
and capitalize
on Symbols:
:hello.upcase
# => :HELLO
:HELLO.downcase
# => :hello
:hello.capitalize
# => :Hello
I’m not entirely sure why would want to call these methods on a Symbol to be honest. I guess the Symbol Class must have them for a reason, but I don’t know what that reason is. If you do, please leave a comment.
However, if you attempt to modify a Symbol you will get an error:
:hello << ' world'
# NoMethodError: undefined method '<<' for :hello:Symbol
So if a Symbol is just an immutable String, why would you use it, and why is there a special distinction in Ruby?
Firstly, one of the big reasons is, as I mentioned above, Symbols are immutable. Unforeseen bugs can crop up in your application when a value can change. If you need to ensure that the value of an object should never change, it’s much safer to use an immutable object.
However, with that being said, it is possible to make a String immutable in Ruby by calling the freeze
method:
name = 'Philip'
# => "Philip"
name.freeze
# => "Philip"
name << 'Jim'
# RuntimeError: can't modify frozen String
As you can see in the example above, once you call the freeze
method on a String instance, you can no longer modify it.
So why else would you use Symbols instead of Strings?
A second reason why you would use a Symbol over a String in certain situations is because Symbols are much better for performance.
For example:
'philip'.object_id
# => 70288511587360
'philip'.object_id
# => 70288504327720
:philip.object_id
# => 539368
:philip.object_id
# => 539368
When you create two String objects with the same value, those two objects are treated as two different objects. When you create a Symbol, referencing the Symbol will always use the same object.
This is much better for performance because the same String object will be created and destroyed over and over again when in reality the same object can just be reused each time.
So hopefully now you understand the characteristics of a Symbol and how they differ from regular String objects.
But when should you use Symbols in your code?
Well Ruby Symbols have a number of characteristics that make them perfect for certain jobs in your day-to-day coding.
You should use Symbols when you want to identify something.
For example a good use case of Symbols is for the keys of a Hash:
person = { name: 'Philip' }
We are identifying the name
key here and so we don’t need the overhead of a mutable String.
Alternatively we would do the following:
person = { 'name' => 'Philip' }
But if we had 100 users, then Ruby would need to make 100 instances of the name
String, when in reality we only need one because it is identifying the key in the Hash.
Another important thing to note is the following syntax:
person = { name: 'Philip' }
The above syntax looks a lot like JSON, however the key name
is actually a Symbol, despite the :
not being at the start of the word. This can be a bit confusing if you don’t know that’s the case.
You will also often see Symbols being used as the name parameters of a method:
Person.age(dob: '1982')
Again this is serving basically the same purpose as you don’t need the overhead of a String to represent the dob
.
Another use case is when you pass a Symbol into a method call, for example:
people = %w[Marty Emmett Bif]
# => ["Marty", "Emmett", "Bif"]
people.send(:pop)
# => "Bif"
When you send the Symbol :pop
into the method send
, the pop
method will be called on the object.
You might also see this same technique used like this:
people.respond_to?(:map)
# => true
In this example we’re sending the :map
to the respond_to?
method. This will check to see if the map
method is available on this object.
Another common usage of Symbols is when defining the attr_accessor
of a Class:
class Dog
attr_accessor :name
end
dog1 = Dog.new
dog1.name = 'Einstein'
In this example we’re sending the :name
to the attr_accessor
method. This basically just automatically creates getters and setters on the class.
Don’t worry about this too much for now as we’ll be covering creating Classes in a future tutorial.
A final common thing you will see Symbols used for is for setting the status of things:
class Car
state :parked
state :driving
state :reversing
end
In the above class we’re saying that the Car
can be in one of three states.
If you are familiar with other languages this is basically the same as using Enums.
Symbols can be confusing for newbie Ruby programmers. You will see Symbols used a lot in Ruby, and so it’s important to really understand why they are used.
Just remember, a Symbol is basically just a string that can’t be changed. This is perfect for identifying things like key’s in a hash that don’t need the overhead of a String
object.
Hopefully if you’ve been struggling with Symbols whilst learning Ruby you will have found this useful. For me, getting my head around when and where to use Symbols was a big step towards fully understanding the Ruby language.