Ruby : Similarity and Difference between Symbol and String with facts

No matter how they behave, Symbol and String in Ruby are alike. We can define Symbols as a type of String ( just according to behavior ). We often use them interchangeably in our code knowingly or unknowingly. Its has been said that Symbols are better than String however many of us are unaware about the core reasons.

Symbols are said to be immutable whereas Strings are mutable objects. Symbols are not garbage-collected. Means the Ruby’s Garbage collector is not going to clear the symbol from memory. If you unnecessarily created huge number of symbols in memory then your ruby process is gonna run out of memory. Try this.

loop {:"#{SecureRandom.hex}"}

What is mutability and immutability in this context? Wait! I am going to answer

Mutable Objects:

Mutable objects can be changed after assignment. In other words, when you have a reference to an instance of an object, the contents of that instance can be altered.

Example:

var1 = "New String"
 => "New String" 
2.1.1 :010 > var1.object_id
 => 20297400 
2.1.1 :011 > var1 << ' appended text'
 => "New String appended text" 
2.1.1 :012 > var1.object_id
 => 20297400

Immutable Objects:

Immutable objects can only be overwritten. In other words, when you have a reference to an instance of an object, the contents of that instance cannot be altered.

2.1.1 :013 > var2 = :string
 => :string 
2.1.1 :014 > var2.object_id
 => 156648 
2.1.1 :015 > var2.replace(:shiva)
NoMethodError: undefined method `replace' 
2.1.1 :016 > var2 << :asd
NoMethodError: undefined method `<<' for :string:Symbol
2.1.1 :017 > var2 << 'asd'
NoMethodError: undefined method `<<' for :string:Symbol

Or Simply try Fixnum.instance_methods or Symbol.instance_methods you can also see no methods with a bang (`!`) except some operators.

Similarity Between String and Symbol

Lets take a look at some valid Strings and their Symbol equivalents.

"hello"
:hello

"hello world"
:"hello world"

bang = "!"

"hello world#{bang}" # => "hello world!"
:"hello world#{bang}" # => :"hello world!"

Also its very easy to convert String to Symbol and vice-versa.

:"hello world".to_s # => "hello world"
"hello world".intern # => :"hello world"

Code nightmare due to Mutability of String

status = "peace"

buggy_logger = status

print "Status: "
print buggy_logger << "\n" # <- This insertion is the bug.

def launch_missile?(status)
  unless status == 'peace'
    return true
  else
    return false
  end
end

def launch_missile
 # codes to launch missile
end

print "Missiles Launched: #{launch_missiles?(status)}\n"

# => Status: peace
# => Missiles Launched: true

There are two ways to handle above mentioned mutability issue. One is defining the Symbol instead of String  and the other is to Freeze the string object.

example = "hello world"
example.upcase!

puts example

example.freeze
example.downcase!

# => "HELLO WORLD"
# => *.rb:7:in `downcase!': can't modify frozen string (TypeError)

String and Symbol Performance differences

require 'benchmark'

str = Benchmark.measure do
  10_000_000.times do
    "test"
  end
end.total

sym = Benchmark.measure do
  10_000_000.times do
    :test
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s
puts

In this example, we are creating 10 million new Strings and then 10 million new Symbols. With the Benchmark library, we can find out how long each activity takes and compare. After running this script three times, I got the following results (yours will most likely be different).

$ ruby benchmark.rb
String: 2.24
Symbol: 1.32

$ ruby benchmark.rb
String: 2.25
Symbol: 1.32

$ ruby benchmark.rb
String: 2.24
Symbol: 1.33

How much faster is comparing Symbols then to Strings? Lets find out:

require 'benchmark'

str = Benchmark.measure do
  10_000_000.times do
    "test" == "test"
  end
end.total

sym = Benchmark.measure do
  10_000_000.times do
    :test == :test
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s
puts

Just like before, I ran this script three times and got the following results.

$ ruby benchmark.rb
String: 4.48
Symbol: 2.39

$ ruby benchmark.rb
String: 4.47
Symbol: 2.39

$ ruby benchmark.rb
String: 4.47
Symbol: 2.38

Some Odd findings

[16] pry(main)> "q".to_sym
=> :q
[17] pry(main)> "payment-gateway_#{Rails.env}_default".to_sym
=> :"payment-gateway_development_default"
[18] pry(main)> "payment-gateway_#{Rails.env}_default".intern
=> :"payment-gateway_development_default"
[19] pry(main)> "#{'q'}".to_sym
=> :q

point to be noted is; `q` is not wrapped with double quotes whereas this is

=> :"payment-gateway_development_default"

The solution

[22] pry(main)> "payment_gateway_#{Rails.env}_default".intern
=> :payment_gateway_development_default

Analysis report:

Using dash (`-`) in the string literals compels ruby specially the `IRB` to wrap the symbol literals with `double quotes`.

# This may create confusion; as `a-b` has different meaning 
=> :payment-gateway_development_default

 

 

References:

http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s