What is the difference between string and symbol?

when should use string and symbol?

I think of a symbol as just a name - it’s not something to be divided up. A string is a sequence of bytes - you can extract words or characters from a string, etc.

There might be a performance difference in matching keywords vs strings, as a keyword is always the same object, whereas strings are different objects, even if they have the same contents.

See the object_id values here:

2.7.2 :010 > "abc".object_id
 => 200 
2.7.2 :011 > "abc".object_id
 => 220 
2.7.2 :012 > "abc".object_id
 => 240 
2.7.2 :013 > :abc.object_id
 => 2044828 
2.7.2 :014 > :abc.object_id
 => 2044828 
2.7.2 :015 > :abc.object_id
 => 2044828

I read somewhere that symbols for hash keys are more performant than strings.

It used to be (before 2.2) symbols were immutable and couldn’t be destroyed once instantiated.

Also, symbols use the same memory slot over and over again.

See this link
https://medium.com/@lcriswell/ruby-symbols-vs-strings-248842529fd9

and this link for more details

Symbols are kind of frozen string literals. You can freeze a string literal with the .freeze() method. The performance of creating string, fronzen string, and symbols are as the following:

string
{:cpu_time=>0.698434, :real_time=>0.6984559019997505}
frozen string
{:cpu_time=>0.4381710000000001, :real_time=>0.43825904999994236}
symbol
{:cpu_time=>0.4134770000000001, :real_time=>0.41361890400003176}

Benchmark code:

def measure_time(mesg = nil)
	epsilon = 1.0e-08
	puts mesg if mesg

	real_t_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
	cpu_t_start = Process.times
	yield
	real_t_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) 
	cpu_t_end = Process.times

	{
		cpu_time: cpu_t_end.utime.+(cpu_t_end.stime).-(
			cpu_t_start.utime + cpu_t_start.stime
		) - epsilon,

		real_time: real_t_end.-(real_t_start) - epsilon
	}
end

p measure_time('string') { 10_000_000.times { 'abcd' } }
p measure_time('frozen string') { 10_000_000.times { 'abcd'.freeze } }
p measure_time('symbol') { 10_000_000.times { :abcd } }

As you can see, creating a string is much faster, the .freeze() is another method call that makes frozen strings slightly slower than symbols. Using #Frozen_String_Literal: true:

string
{:cpu_time=>0.41340199000000005, :real_time=>0.4134406739998519}
frozen string
{:cpu_time=>0.41783299, :real_time=>0.41783457300005805}
symbol
{:cpu_time=>0.4159899900000001, :real_time=>0.4159993810002413}

As you can see, the difference is minute between immutable frozen string and symbol.

But I have seen people using Symbols like this:

symbol_ary = %i[a b c]
some_var = symbol_ary[1].to_s

This is really common in rails world, and this has no benefit at all, quite the opposite, it’s slower. Don’t use such bad code. There are some other rare code that properly uses symbols, but the real common usage of symbol is creating hashes - unless you of course convert user input strings to symbols and check the hash!