Yes, Ruby would be better without Symbols

Logo

Robert Pankowecki published a fascinating article titled “Could we drop Symbols from Ruby?”.

For me symbols are one of the most annoying features in Ruby so I instantly thought “Yes! That’s a great idea”.

But then I started reading comments on the blog and over the Internet and it turned out that the feedback is rather negative. Honestly, it’s quite surprising to me.

What’s the problem

First, let me quickly describe the problem.

Currently, symbols and strings look similar but they’re not equal.

:foo == "foo" # false

It’s inconvenient. Especially when working with hashes.

hash = {age: 32}

hash[:age] # 32
hash["age"] # nil

That’s the reason why workarounds such as HashWithIndifferentAccess exist.

It was even a source of a critical security bug. See CVE-2013-1854

Solution

Robert’s idea is to turn symbols into immutable strings. They would still exist but only as a syntactic sugar.

hash = {age: 32}

hash[:age] # 32
hash["age"] # 32

Legacy

So, why do symbols exist? What are the differences between symbols and strings?

Historically they differed significantly. However each new version of Ruby makes the gap between symbols and strings smaller and smaller.

Strings are garbage collected while Symbols are not

This isn’t true anymore. Since Ruby 2.2 both symbols and strings are GC’ed.

Strings are mutable. Symbols are immutable.

Nowadays this is true although we’re in the process of making strings immutable by default. Ruby 2.3 introduced the frozen_string_literal which makes Strings immutable. In Ruby 3.0 all strings will be most likely immutable by default.

Symbols are faster than strings

Indeed, symbols are mapped to integers hence they are faster than strings. I don’t know how hard it would be to come up with as solution to this problem. Given that strings will be immutable maybe it’s possible to close the performance gap? Also, Ruby 3.0 will hopefully ship with a JIT which could help in this regard.

Backwards compatibiliy

This is an interesting problem. Turning symbols into immutable strings wouldn’t be fully backwards compatible. Why? Have a look at this snippet

case foo
when String then puts "foo is a string"
when Symbol then puts "foo is a symbol"
end

If symbols were just strings then :some_symbol.is_a?(String) would return true but currently it returns false. How many apps and libraries would be affected by this change? I don’t know but a quick research reveals that ActiveRecord relies on such comparisions. See this line and that line.

Most likely there are would be more tools that would break.

Robert proposes that Symbol could inherit from String. It solves the problem but only partially.

This would work as expected

case foo
when Symbol then puts "foo is a symbol"
when String then puts "foo is a string"
end

but this would not

case foo
when String then puts "foo is a string"
when Symbol then puts "foo is a symbol"
end

Now, backwards compatibiliy is one of top reasons I stick to Ruby. Almost always it’s a breeze to upgrade from one version to another. The only non-trivial migration was Ruby 1.8 to 1.9. Compare it to Python. Python 3.0 was released in 2008 but it still hasn’t been widely adopted due to incompatible changes with 2.7.

ruby-lang.org advertises Ruby as a programmer’s best friend. And it really is. Backwards compatibiliy and harmless upgrades are major factors that contribute to this fact. We shouldn’t trade them easily.

It’s a though choice. Would I sacrifice backwards compatibiliy to remove one of the biggest annoyances? Obviously it depends but if dropping symbols wouldn’t impact significant part of the ruby ecosystem then my answer would be “yes”.

Don’t stop

At Euruko 2017 Matz said that “Ruby is a community effort”. I read this as an encouragement to share ideas, make contributions and shape the future Ruby. This is great power and I hope to read more propsals on how to make Ruby better. No matter whether they’re feasible or not.

comments powered by Disqus