The article is a bit confusing, but I like the concept behind symbols: Basically a way to distinguish identifier names (chosen by the programmer) from user-facing strings.
The distinction between is one I've mentally adopted in other languages like Python as well. For personal projects I like to use single quotes for 'symbols' and double quotes for "strings", e.g.:
I almost never use single quotes unless I’m in someone else’s code that already used single quotes. And then I have to fight the urge to not change everything to double quotes just because.
Symbols are special cased pseudo-strings for langages which have either extremely inefficient strings (erlang) or mutable ones (ruby). That’s about the extent of it.
Python or Java don’t really have a use for them because their strings are immutable, and likely interned when metaprogramming is relevant (e.g. method or attribute names).
Whenever Perl encounters a string literal in the code (especially one used as a hash key or a bareword), it often "interns" it. This means it stores a single, canonical, read-only copy of that string in a memory pool.
That's the core idea, and then Ruby has surface stuff like the symbol syntax and class. I'm pretty sure it's fine to use strings as hash keys though if you like.
An irrelevant implementation detail. Interned strings are also stored globally, and depending on implementations interned strings may or may not be subject to memory reclaiming.
> Ruby does not care for efficiency, so there is no point to argue for symbols vs string performance
Which is why Ruby's having symbols is associated with mutable strings, not inefficient strings.
And there's a gulf between not caring too much about efficiency and strings being a linked list of integers, which is what they are in Erlang.
They're not. A symbol is an arbitrary identifier, which can be used to point to system elements (e.g. classes, methods, etc...). These are all things you can do just fine with immutable interned strings. Which is exactly what languages which have immutable interned strings do.
You'd just have a broken VM if you used mutable strings for metaprogramming in Ruby, so it needs symbols. Both things it inherited from all of Perl, Smalltalk, and Lisp.
How so? Quite literally symbols are used as an immutable string with a shorter syntax. So much so that I've been finding their literal constraints limiting lately.
Almost the entire value of symbols separate from strings is at the level of programmer communication rather than PL semantics.
It tells a reader of the code that this term is arbitrary but significant, probably represents an enmeshment with another part of the code, and will not be displayed to any user. When seeing a new term in code that is a lot of the things you're going to need to figure out about it anyway. It's a very valuable & practical signal.
If you need to mutate or concat or interpolate or capitalize or any other string operation it, it probably shouldn't be a symbol anymore, or shouldn't have been to start with.
> Almost the entire value of symbols separate from strings is at the level of programmer communication rather than PL semantics.
That's the opposite of reality. Symbols are necessitated by PL semantics, which is why languages which don't have those problematic string semantics tend to not bother with symbols.
> It tells a reader of the code that this term is arbitrary but significant
That you can do that with symbols is not why they exist (you can need to associate additional semantics with pretty much any first or third-party type after all, that's why the newtype pattern is so popular in modern statically typed languages).
And it's not like you need formal symbols to do it in the first place. For instance like an other nearby commenter in Python I do that by using single and double-quoted strings, having taken up that habit from Erlang (where symbols are single quoted and strings are double quoted).
> And it's not like you need formal symbols to do it in the first place.
I mean we don't need any of this shit. Go take a long bath and then write some assembly I don't care. Symbols are a useful tool in some languages, for the reasons I described. That you're creating ad hoc quoting conventions to recover part of the functionality of this feature in languages that don't have it is a pretty strong signal I'm correct! Opposite of reality my ass lol.
It's completely true of lisp. Lisp strings are generally mutable (although literal strings may be interned and non-mutable, or even worse mutation of literal strings may be UB). Same for Smalltalk.
Well, it's a string that will guarantee unique allocations (two identical strings are guaranteed to be allocated at the same address), which makes equality checks super fast (compare pointers directly). But pretty much just a string nonetheless...
String can be interned in Ruby, but it’s always possible for an equal string that is not interned to also exist. Hence interned strings can’t benefit from the same optimization than symbols.
You can first compare them by pointer, but on a mismatch you have to fallback to comparing content. Same for the hashcode, you have to hash the content.
I'm talking about as a user of the language, not as a language designer. I have an unpopular opinion about this, but symbols are error prone and these optimizations are ultimately not worth it.
I'm a ruby developer, would never dream of switching to another language as my bread and butter - but my language design hot take for ruby is that symbols were a mistake and unnecessary and the language would have been better off just using frozen (immutable) string literals for everything except for the syntax of keyword arguments.
Unfortunately we can't change it now, but generally I just don't use symbols anymore unless I absolutely have to.
I've always been conflicted on them. I like that they allow me to be more explicit about things which are read from input or parsed out as strings vs internal parts of my program (I rarely call .to_sym), but I've also lost time to being careless and indexing hash maps with the wrong type.
user = { name: "Alice", age: 30 }
puts user[:name] # Alice
puts user["name"] # nil
I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.
It would be great if Ruby would finally have immutable strings by default and, at that point, it would be possible to make symbols be strings.
This would prevent any such user[:name] vs user["name"] bugs while not breaking any other functionality. And also keeping the memory "optimized" by reusing a single immutable string.
Rails makes this more confusing with HashWithIndifferentAccess[1]. People coming from Rails are often confused by this when working with straight ruby and this kind of hash access doesn’t work.
> I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.
Yeah, that is true. It adds a cognitive load onto the ruby developer writing the code as well. Personally I prefer symbols as keys in a Hash, mostly because I like symbols, I assume it may be faster usually (this depends on factors, such as how many symbols one uses, the garbage collection kicking off and so forth, but by and large I think for most use cases, Symbols are simply more efficient). We also have abominations such as HashWithIndifferentAccess; Jeremy wrote an article why that is not good (indirectly, e. g. the article he wrote was about Symbols more, their use cases and differences to Strings, but from this it follows that HashWithIndifferentAccess is not a good idea. While I agree, I think some people simply don't want to have to care either way).
If I need to query a hash often, I tend to write a method, and the method then makes sure any input is either a string or a symbol for that given Hash.
> It would be great if Ruby would finally have immutable strings by default
But it has. I still use "# frozen_string_literal: true", but if you omit it, the Strings are frozen by default. People could set "# frozen_string_literal: true" in a .rb file if they want to retain the old behaviour.
> it would be possible to make symbols be strings.
But Symbols are not Strings. And bugs based on x[:foo] versus x['foo'] are always going to happen. They are very easy to avoid though. I don't really run into these in my own code, largely because I settled on symbols as keys for a Hash.
> And also keeping the memory "optimized" by reusing a single immutable string.
But a Symbol is not a String. Not even an immutable String. I understand what you mean (and internally it may be that way already, actually), but it is not a String.
I also prefer symbols as keys in hash. It just looks more aesthetically pleasing. :)
I think the optimization string vs symbol is negligent in most of the apps. If you need that level of optimization, you should probably switch to Rust.
> If I need to query a hash often, I tend to write a method, and the method then makes sure any input is either a string or a symbol for that given Hash.
This is terrible. This is the exact opposite of what Ruby is trying to achieve: developer happiness. You basically implement "symbol is a string" for hashes (aka HashWithIndifferentAccess).
> But it has. I still use "# frozen_string_literal: true", but if you omit it, the Strings are frozen by default.
This is not the case. If you omit "# frozen_string_literal: true", the strings are mutable, in all versions of Ruby, even in Ruby 4.0, which will be released on 25 Dec.
> But a Symbol is not a String. Not even an immutable String. I understand what you mean (and internally it may be that way already, actually), but it is not a String.
If it walks like a duck and quacks like a duck...
Who cares? What's the difference it makes for you whether symbols and string are interchangeable? Show me one valid use-case where having symbols and strings being different (user[:name] vs user["name"], or attr_reader "name") is useful.
Agreed. However had it should also be mentioned that this originated from rails.
Many bad things originate from the rails ecosystem. (Arguably some good too, but I am very pessimistic ever since shopify's recent powermove and DHH side-line commenting off-the-fence while being on shopify's board.)
The article is a bit confusing, but I like the concept behind symbols: Basically a way to distinguish identifier names (chosen by the programmer) from user-facing strings.
The distinction between is one I've mentally adopted in other languages like Python as well. For personal projects I like to use single quotes for 'symbols' and double quotes for "strings", e.g.:
Does anyone else do something similar?Single vs double quotes is also semantically significant in Ruby. Double quoted strings support interpolation whereas single quoted does not.
I almost never use single quotes unless I’m in someone else’s code that already used single quotes. And then I have to fight the urge to not change everything to double quotes just because.
> Does anyone else do something similar?
I do exactly the same thing. Did you also derive it from Erlang?
OP is really tying themselves into knots.
Symbols are special cased pseudo-strings for langages which have either extremely inefficient strings (erlang) or mutable ones (ruby). That’s about the extent of it.
Python or Java don’t really have a use for them because their strings are immutable, and likely interned when metaprogramming is relevant (e.g. method or attribute names).
Symbol is the only feature I miss after switching to Python. It makes code so much more readable to distinguish keys and normal strings.
Much like yccs27 above, I do that using single and double quoted strings (a habit I got from Erlang).
Whenever Perl encounters a string literal in the code (especially one used as a hash key or a bareword), it often "interns" it. This means it stores a single, canonical, read-only copy of that string in a memory pool.
That's the core idea, and then Ruby has surface stuff like the symbol syntax and class. I'm pretty sure it's fine to use strings as hash keys though if you like.
Could not read OP, clownflare is down.
> Symbols are pseudo-strings
Can guess by LISP: Symbols reside in the symbol table, which is global scope. Strings reside in the variable tables, subject to local scope.
It is two different storage mechanisms
> inefficient strings
Ruby does not care for efficiency, so there is no point to argue for symbols vs string performance
> Ruby does not care for efficiency, so there is no point to argue for symbols vs string performance
Symbols existed entirely for performance reasons and were once never GC'd: this is absolutely Ruby "car[ing] for efficiency."
Today, Ruby's Symbol is GC'd (and is much closer to 'String' in practicality) but still has enormous impacts on performance.
> It is two different storage mechanisms
An irrelevant implementation detail. Interned strings are also stored globally, and depending on implementations interned strings may or may not be subject to memory reclaiming.
> Ruby does not care for efficiency, so there is no point to argue for symbols vs string performance
Which is why Ruby's having symbols is associated with mutable strings, not inefficient strings.
And there's a gulf between not caring too much about efficiency and strings being a linked list of integers, which is what they are in Erlang.
What about LISPs? They have atoms too.
Generally mutable strings there too, like Ruby
This is silly. The semantics are entirely different!
> This is silly.
Oh my bad, great counterpoint.
> The semantics are entirely different!
They're not. A symbol is an arbitrary identifier, which can be used to point to system elements (e.g. classes, methods, etc...). These are all things you can do just fine with immutable interned strings. Which is exactly what languages which have immutable interned strings do.
You'd just have a broken VM if you used mutable strings for metaprogramming in Ruby, so it needs symbols. Both things it inherited from all of Perl, Smalltalk, and Lisp.
How so? Quite literally symbols are used as an immutable string with a shorter syntax. So much so that I've been finding their literal constraints limiting lately.
Almost the entire value of symbols separate from strings is at the level of programmer communication rather than PL semantics.
It tells a reader of the code that this term is arbitrary but significant, probably represents an enmeshment with another part of the code, and will not be displayed to any user. When seeing a new term in code that is a lot of the things you're going to need to figure out about it anyway. It's a very valuable & practical signal.
If you need to mutate or concat or interpolate or capitalize or any other string operation it, it probably shouldn't be a symbol anymore, or shouldn't have been to start with.
> Almost the entire value of symbols separate from strings is at the level of programmer communication rather than PL semantics.
That's the opposite of reality. Symbols are necessitated by PL semantics, which is why languages which don't have those problematic string semantics tend to not bother with symbols.
> It tells a reader of the code that this term is arbitrary but significant
That you can do that with symbols is not why they exist (you can need to associate additional semantics with pretty much any first or third-party type after all, that's why the newtype pattern is so popular in modern statically typed languages).
And it's not like you need formal symbols to do it in the first place. For instance like an other nearby commenter in Python I do that by using single and double-quoted strings, having taken up that habit from Erlang (where symbols are single quoted and strings are double quoted).
> And it's not like you need formal symbols to do it in the first place.
I mean we don't need any of this shit. Go take a long bath and then write some assembly I don't care. Symbols are a useful tool in some languages, for the reasons I described. That you're creating ad hoc quoting conventions to recover part of the functionality of this feature in languages that don't have it is a pretty strong signal I'm correct! Opposite of reality my ass lol.
That’s really not true for Lisp.
Ruby, like its predecessor Perl, is one of the finer examples of Greenspunning and shows a lot of Lisp influence.
Unfortunately I can’t read the actual submission right now due to the cloudflare outage.
> That’s really not true for Lisp.
It's completely true of lisp. Lisp strings are generally mutable (although literal strings may be interned and non-mutable, or even worse mutation of literal strings may be UB). Same for Smalltalk.
OP here, you can read it here https://github.com/stonecharioteer/tech-blog/blob/main/conte...
I think the whole article is super confusing.
A Symbol is really just a string!
Well, it's a string that will guarantee unique allocations (two identical strings are guaranteed to be allocated at the same address), which makes equality checks super fast (compare pointers directly). But pretty much just a string nonetheless...
A symbol really is just an integer which happens to have a fancy name when looked at from a user's point of view.
frozen string literals also have unique allocations now, so symbols are kindof redundant now
No they’re not.
String can be interned in Ruby, but it’s always possible for an equal string that is not interned to also exist. Hence interned strings can’t benefit from the same optimization than symbols.
You can first compare them by pointer, but on a mismatch you have to fallback to comparing content. Same for the hashcode, you have to hash the content.
I'm talking about as a user of the language, not as a language designer. I have an unpopular opinion about this, but symbols are error prone and these optimizations are ultimately not worth it.
I still wish the Rails enums were written with slightly more object portability.
I'm a ruby developer, would never dream of switching to another language as my bread and butter - but my language design hot take for ruby is that symbols were a mistake and unnecessary and the language would have been better off just using frozen (immutable) string literals for everything except for the syntax of keyword arguments.
Unfortunately we can't change it now, but generally I just don't use symbols anymore unless I absolutely have to.
I've always been conflicted on them. I like that they allow me to be more explicit about things which are read from input or parsed out as strings vs internal parts of my program (I rarely call .to_sym), but I've also lost time to being careless and indexing hash maps with the wrong type.
Overall, I think I'm glad they exist.
> Dang it, Ruby. You never cease to amaze me!
This has been true forever.
[dead]
Symbols are a foot gun.
> Symbols aren’t interchangeable though.
I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.It would be great if Ruby would finally have immutable strings by default and, at that point, it would be possible to make symbols be strings. This would prevent any such user[:name] vs user["name"] bugs while not breaking any other functionality. And also keeping the memory "optimized" by reusing a single immutable string.
That’s not much more of a foot gun than:
Symbols are always taught as a first class object in Ruby, not just syntactic sugar for accessing hashes. “foo” does not equal :fooRails makes this more confusing with HashWithIndifferentAccess[1]. People coming from Rails are often confused by this when working with straight ruby and this kind of hash access doesn’t work.
1. https://api.rubyonrails.org/classes/ActiveSupport/HashWithIn...
> I'm 100% convinced that every Ruby developer has at least once made a bug where they tried to access a hash entry using a symbol, where the key was actually a string or vice-versa.
Yeah, that is true. It adds a cognitive load onto the ruby developer writing the code as well. Personally I prefer symbols as keys in a Hash, mostly because I like symbols, I assume it may be faster usually (this depends on factors, such as how many symbols one uses, the garbage collection kicking off and so forth, but by and large I think for most use cases, Symbols are simply more efficient). We also have abominations such as HashWithIndifferentAccess; Jeremy wrote an article why that is not good (indirectly, e. g. the article he wrote was about Symbols more, their use cases and differences to Strings, but from this it follows that HashWithIndifferentAccess is not a good idea. While I agree, I think some people simply don't want to have to care either way).
If I need to query a hash often, I tend to write a method, and the method then makes sure any input is either a string or a symbol for that given Hash.
> It would be great if Ruby would finally have immutable strings by default
But it has. I still use "# frozen_string_literal: true", but if you omit it, the Strings are frozen by default. People could set "# frozen_string_literal: true" in a .rb file if they want to retain the old behaviour.
> it would be possible to make symbols be strings.
But Symbols are not Strings. And bugs based on x[:foo] versus x['foo'] are always going to happen. They are very easy to avoid though. I don't really run into these in my own code, largely because I settled on symbols as keys for a Hash.
> And also keeping the memory "optimized" by reusing a single immutable string.
But a Symbol is not a String. Not even an immutable String. I understand what you mean (and internally it may be that way already, actually), but it is not a String.
I also prefer symbols as keys in hash. It just looks more aesthetically pleasing. :) I think the optimization string vs symbol is negligent in most of the apps. If you need that level of optimization, you should probably switch to Rust.
> If I need to query a hash often, I tend to write a method, and the method then makes sure any input is either a string or a symbol for that given Hash.
This is terrible. This is the exact opposite of what Ruby is trying to achieve: developer happiness. You basically implement "symbol is a string" for hashes (aka HashWithIndifferentAccess).
> But it has. I still use "# frozen_string_literal: true", but if you omit it, the Strings are frozen by default.
This is not the case. If you omit "# frozen_string_literal: true", the strings are mutable, in all versions of Ruby, even in Ruby 4.0, which will be released on 25 Dec.
> But a Symbol is not a String. Not even an immutable String. I understand what you mean (and internally it may be that way already, actually), but it is not a String.
If it walks like a duck and quacks like a duck... Who cares? What's the difference it makes for you whether symbols and string are interchangeable? Show me one valid use-case where having symbols and strings being different (user[:name] vs user["name"], or attr_reader "name") is useful.
When one consistently uses symbols for keys and strings for data then when you notice a `user[<String>]` it is a very visible, obvious mistake.
Here's a fantastic writeup about frozen strings in Ruby and the upcoming changes: https://byroot.github.io/ruby/performance/2025/10/28/string-...
I'd guess that the majority of people who've made a bug like this got started on Ruby via Rails, where many hashes are HashWithIndifferentAccesses.
HWIAs are convenient, but they do confuse the issue.
Sure, we wouldn't have ActiveSupport::HashWithIndifferentAccess if it wasn't an occasional issue.
HashWithIndifferentAccess was added because back in the day symbols were immortal, hence could not be used for request parameters.
It no longer make sense today, and any new use of it is a smell.
Agreed. However had it should also be mentioned that this originated from rails.
Many bad things originate from the rails ecosystem. (Arguably some good too, but I am very pessimistic ever since shopify's recent powermove and DHH side-line commenting off-the-fence while being on shopify's board.)
Rails has paid my salary for the best part of 20 years on and off. I'm OK with it. ;)
The foot-gun there is dynamic typing, not symbols.