Explain the difference between strings and symbols like I am five

I'm having a hard time understanding the underlying technical difference between Strings and Symbols in Ruby. Any help?

Did you find this post useful? Show some love!

Every time you say :rose you are referring to literally the same flower. Every "rose" might look and feel the same, but it is not the same flower.

In a lot of cases it doesn't matter whether it's the same or a copy but if you have to gather up all the roses in the garden. The symbols can fit in your hand. The strings might take up a whole shipping container.

A symbol of name :rose is literally the same object as every other symbol :rose in the application.

irb(main):007:0> :rose.object_id
=> 1146588
irb(main):008:0> :rose.object_id
=> 1146588
irb(main):009:0> :rose.object_id
=> 1146588

The string "rose" on the other hand is new every time.

irb(main):010:0> "rose".object_id
=> -70361110657240
irb(main):011:0> "rose".object_id
=> -70361110657240
irb(main):012:0> "rose".object_id
=> -70361110657240

Symbols are immutable which can have a positive impact on your application's speed and reliability. Once a symbol is declared, it's value is constant.

Hope that helps.

I know you're trying to prove that strings are different each time, but in your example they're all the same.

Did you have the "frozen string literal" thing still turned on?

Following the example, I think that Ben was trying to show the difference between Strings and Symbols. Something like this:


$ irb
> "a string".object_id
=> 70358630335100
> "a string".object_id
=> 70358640625960
> "a string".object_id
=> 70358644379620

As you can see, even though the 3 Strings created are exactly the same, every new String created has a different object_id: They’re actually different objects, even though they contain the same text.

With Symbols however:


$ irb
> :a_symbol.object_id
=> 1086748
> :a_symbol.object_id
=> 1086748
> :a_symbol.object_id
=> 1086748

We now get the same object_id for each of the Symbols, meaning they’re referring to the exact same object.

Yes. It make a lot more sense, symbols are unique identifiers that are considered code, not data. Thanks Ben!

One interesting caveat to this, is that you can make strings behave the same as a symbol in this case with a magic comment.

I just created a Ruby file that looks like this:

# frozen_string_literal: true

puts :rose.object_id
puts :rose.object_id

puts "rose".object_id
puts "rose".object_id

When this is run on the command line, the results are:

1009628
1009628
70288684550700
70288684550700

The comment frozen_string_literal: true means that whenever you use the string literal in this file, that is define a string using quote marks, that string is then immutable and a constant reference just like a symbol.

Strings created in other ways than the literal remain mutable and distinct objects though.

# frozen_string_literal: true

puts String.new("rose").object_id
puts String.new("rose").object_id

results in:

70158463624980
70158463624920

Thanks Phil! What's a good case when you should use frozen_string_literal: true?

The benefits are twofold.

Firstly, memory and performance. If you use the magic comment within a file then every time you use a string literal the string will only be created once and in one location in memory. Further use of the string literal will just point to that initial memory location saving the time of creating a new object and the space of allocating new memory.

Secondly, your string literals are now immutable. This can make your programs easier to reason about. There is a good example in this post which asks what is data after this code runs?

data = "hello"
save(data)
puts data
# => ???

With a mutable string, the save method can change the string data and without knowing the implementation of save you cannot know what data will look like afterwards.

If you use the magic comment, or you freeze the string manually (data = "hello".freeze), then there are only two possible outcomes: data will be the same or save will throw a RuntimeError because it tried to change data.

Finally, it is a good idea to start working with frozen string literals because in Ruby 3 this will be the default behaviour. So it's time to get used to this change so you can be ready for Ruby 3.

Thanks for outlining the benefits. As I understand, the primary motivation for making this change is performance but also the inherent benefit of making your code easier to read and manage. Although, the introduction of frozen string literals itself feels a bit strange. There’s something slightly odd about string literals—and only string literals—becoming immutable by default.

I think I can see why this is only the case for string literals. Firstly, string literals get used all over the place, in constants and as hash keys particularly, and those gain the most benefit from being frozen. However, given the existence and wide usage of Ruby's mutable strings, making all strings immutable would be a huge breaking change (even this is a breaking change, but the magic comment does allow us to test and get into it gently before Ruby 3). It also means we continue to have easy ways to create a mutable string, using String.new or "string".dup.

Don't forget, if you create a string another way and want it to be frozen, you can still call freeze on it and gain the same benefits.

Ruby calls Symbols "internalized strings", a form of string interning in computer-science terms. What makes Ruby fairly unique is that it has a special notation for them, and uses them pervasively in a lot of code.

A Ruby string is a series of bytes stored in a data structure that's typically mutable unless "frozen". Even when frozen it's still a series of bytes.

Comparing arbitrary string A to arbitrary string B requires a fair amount of computation. Not only do you need to check letter by letter, but you also need to account for the quirks of UTF-8 where there might be more than one byte-level representation of the same character. On the whole this is a fairly messy process even if, by human standards, it takes place in an instant. Making matters worse, string A might match string B right now, but a second later and they might be different because of changes. That means you need to compare strings every single time to know if they match or not.

Symbols are by definition both unique and immutable. This makes it trivial to compare one symbol to another: If they are the same object they're a match. If not, they're different. You don't even need to inspect the content of the symbols to know if they match, comparing object_id values is sufficient. If two values matched once, then they will always match, because by definition they're exactly the same.

What this means is Symbols are cheap, really cheap, as they're effectively just a pointer, around 4 or 8 bytes, per instance in your code. If you have a million hashes each with the same symbol key the cost is marginal.

With Strings you may have a lot of duplication, and the cost of storage is proportional to the length of the string times the number of instances. That can add up to a lot of memory if you use it as a Hash key.

Generally you use Symbol values as arguments to methods, common keys in hashes, and enum-like flags. You use String for values that are unique or infrequent even if those values appear in hashes (e.g. UUIDs) as well as storing user input.

One of the most baffling thing to a Ruby beginner is that :x != "x" just like 0 != "0" as Ruby won't casually convert things for you under most circumstances. Once you understand the reason behind Symbols you can use them effectively and design code that's quick, light-weight, and efficient.

Classic DEV Post from Apr 16

Best websites for free gradients and color schemes/palettes for faster workflow

Colors and Gradients

READ POST
Follow @hitarthchudgar to see more of their posts in your feed.
dev.to is now open source!
View Announcement Post View GitHub Repo
laurosilvacom
I design, build, and maintain Ruby on Rails web apps. I aslo write technical publications on web development and remote work.
More from @laurosilvacom
Explain pass-by-reference and pass-by-value (in Ruby) like I am five
#explainlikeimfive #ruby
Trending on dev.to
Explain Grunt Task Runner Like I'm Five
#explainlikeimfive #javascript #discuss
Follow Me, Rubyists, Because...
#testing #ruby #github #debugging
Explain Blockchains, Cryptocurrencies Like I'm Five
#explainlikeimfive
Pro tips: Alternatives to "rails routes | grep bars"
#rails #ruby #tips
Explain Hashing + salting Like I'm Five
#explainlikeimfive #webdev
What are/were your go to resources for learning Ruby and Rails?
#discuss #ruby