Why is it not a good idea to dynamically create a lot of symbols in ruby (for versions before 2.2)?
Solution 1:
Symbols are like strings but they are immutable - they can't be modified.
They are only put into memory once, making them very efficient to use for things like keys in hashes but they stay in memory until the program exits. This makes them a memory hog if you misuse them.
If you dynamically create lots of symbols, you are allocating a lot of memory that can't be freed until your program ends. You should only dynamically create symbols (using string.to_sym
) if you know you will:
- need to repeatedly access the symbol
- not need to modify them
As I said earlier, they are useful for things like hashes - where you care more about the identity of the variable than its value. Symbols, when correctly used, are a readable and efficient way to pass around identity.
I will explain what I mean about the immutability of symbols RE your comment.
Strings are like arrays; they can be modified in place:
12:17:44 ~$ irb
irb(main):001:0> string = "Hello World!"
=> "Hello World!"
irb(main):002:0> string[5] = 'z'
=> "z"
irb(main):003:0> string
=> "HellozWorld!"
irb(main):004:0>
Symbols are more like numbers; they can't be edited in place:
irb(main):011:0> symbol = :Hello_World
=> :Hello_World
irb(main):012:0> symbol[5] = 'z'
NoMethodError: undefined method `[]=' for :Hello_World:Symbol
from (irb):12
from :0
Solution 2:
A symbol is the same object and the same allocation of memory no matter where it is used:
>> :hello.object_id
=> 331068
>> a = :hello
=> :hello
>> a.object_id
=> 331068
>> b = :hello
=> :hello
>> b.object_id
=> 331068
>> a = "hello"
=> "hello"
>> a.object_id
=> 2149256980
>> b = "hello"
=> "hello"
>> b.object_id
=> 2149235120
>> b = "hell" + "o"
Two strings which are 'the same' in that they contain the same characters may not reference the same memory, which can be inefficient if you're using strings for, say, hashes.
So, symbols can be useful for reducing memory overhead. However - they are a memory leak waiting to happen, because symbols cannot be garbage collected once created. Creating thousands and thousands of symbols would allocate the memory and not be recoverable. Yikes!
Solution 3:
It can be particularly bad to create symbols from user input without validating the input against some kind of a white-list (for example, for query string parameters in RoR). If user input is converted to symbols without validation, a malicious user can cause your program to consume large amounts of memory that will never be garbage collected.
Bad (a symbol is created regardless of user input):
name = params[:name].to_sym
Good (a symbol is only created if the user input is allowed):
whitelist = ['allowed_value', 'another_allowed_value']
raise ArgumentError unless whitelist.include?(params[:name])
name = params[:name].to_sym
Solution 4:
Starting Ruby 2.2 and above Symbols are automatically garbage collected and so this should not be an issue.
Solution 5:
If you are using Ruby 2.2.0 or later, it should usually be OK to dynamically create a lot of symbols, because they will be garbage collected according to the Ruby 2.2.0-preview1 announcement, which has a link to more details about the new symbol GC. However, if you pass your dynamic symbols to some kind of code that converts it to an ID (an internal Ruby implementation concept used in the C source code), then in that case it will get pinned and never get garbage collected. I'm not sure how commonly that happens.
You can think of symbols as a name of something, and strings (roughly) as a sequence of characters. In many cases you could use either a symbol or a string, or you could use a mixture of the two. Symbols are immutable, which means they can't be changed after being created. The way symbols are implemented, it is very efficient to compare two symbols to see if they are equal, so using them as keys to hashes should be a little faster than using strings. Symbols don't have a lot the methods that strings do, such as start_with?
so you would have to use to_s
to convert the symbol into a string before calling those methods.
You can read more about symbols here in the documentation:
http://www.ruby-doc.org/core-2.1.3/Symbol.html