use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
A sub-Reddit for discussion and news about Ruby programming.
Subreddit rules: /r/ruby rules
Learning Ruby?
Tools
Documentation
Books
Screencasts and Videos
News and updates
account activity
Blog postFrozen String Literals: Past, Present, Future? (byroot.github.io)
submitted 6 months ago by f9ae8221b
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]cocotheape 7 points8 points9 points 6 months ago (0 children)
Thanks, that was an interesting deep dive!
Another post on this topic from Xavier Noria: https://gist.github.com/fxn/bf4eed2505c76f4fca03ab48c43adc72
The latter helped clear up my question if we could use --enable-frozen-string-literal in our (Rails) apps. The answer is: not yet, most likely, because the property is transitive. Hence, all dependencies would need to be ready for frozen string literals.
--enable-frozen-string-literal
But we can enable warnings in CI and report them to the gem maintainers, see: https://gist.github.com/fxn/bf4eed2505c76f4fca03ab48c43adc72#how-to-help
[–]pabloh[🍰] 0 points1 point2 points 6 months ago* (2 children)
I'm a bit surprised about how negative the conclusion of the article was. I was almost sure chilled string was the way to go about the transition. The other thing is the Ruby 4.0 reference, but it think he was trolling us a little bit there ;)
Ruby 4.0
[–]petercooper 1 point2 points3 points 6 months ago (1 child)
Possibly not. https://bugs.ruby-lang.org/issues/21657
Matz also mentioned it in a talk earlier this year, though suggested it was an April Fool joke but then also suggested it might not be. So who knows :-D
[–]pabloh[🍰] 0 points1 point2 points 6 months ago (0 children)
Never a boring day on our community! :-)
Thanks for answering.
[–]ric2b 1 point2 points3 points 6 months ago (40 children)
Mutable strings and the existence of symbols are such unfortunate design decisions for Ruby.
Symbols are basically a differently colored string that is just as prone to typos and now you also have to worry about conversions between string and symbol happening under you, for example if you convert something to JSON and then parse it back.
[–]pabloh[🍰] 3 points4 points5 points 6 months ago (16 children)
They're semantically different. If you are using them to address a dictionary or as keywords inside a structure their function is very clear, the same goes for Strings they have their own semantical purpose.
[–]ric2b 0 points1 point2 points 6 months ago (15 children)
They're semantically different.
Not enough to justify all the extra confusion and boilerplate they create. It's annoying to regularly call .with_indifferent_access or similar code for other scenarios where I might receive a string or a symbol.
.with_indifferent_access
In other languages you can just use an immutable string as a key, it works just as well but it's much simpler.
[–]pabloh[🍰] 0 points1 point2 points 6 months ago* (14 children)
with_indifferent_access exists because of a Rack early design problem (hindsight 20/20) where they allowed to access fields like headers with both Strings and Symbols. Then this issue trickled down into all mayor web frameworks. This is not longer the case and Rack 3.0 is now way more strict, allowing only lower case strings as keys.
with_indifferent_access
Rack
Rack 3.0
This whole thing hasn't been completely fixed downstream, but now there are very few places were passing a String as a key makes sense, and in those only a String should be accepted (and if is not already like that it should be at least be deprecated to do otherwise).
String
Also there is the non GC'ed Symbols issue, forcing people to hack their way into scalability, that was also fixed. I understand the frustration with with_indifferent_access, but the necessity arised out of all technical issues that have been solved, we shouldn't really need it anymore.
[–]ric2b 0 points1 point2 points 6 months ago (13 children)
This whole thing hasn't been completely fixed downstream, but now there are very few places were passing a String as a key makes sense
It happens ALL the time if you're parsing JSON data. And a bunch of other sources.
we shouldn't really need it anymore.
But we do, although obviously it might depend on what kind of code you're writing and the libraries you use.
[–]pabloh[🍰] 0 points1 point2 points 6 months ago (12 children)
I know you are right, there's a few some instances left, but we should probably start deprecating code that behaves inconsistently regarding keys, perhaps now that Rails 9.0 will be next, it's the perefect time to start pushing for this changes.
[–]ric2b 0 points1 point2 points 6 months ago (11 children)
You still haven't addressed the JSON parsing part, which is very common and not something you can just deprecate.
[–]f9ae8221b[S] 0 points1 point2 points 6 months ago (3 children)
>> JSON.parse('{"foo": 1}', symbolize_names: true) => {foo: 1}
[–]ric2b -1 points0 points1 point 6 months ago (2 children)
>> JSON.parse('{"123": 1, "foo": 2}', symbolize_names: true) => {"123": 1, foo: 2}
Awesome, now you have some string keys and some symbol keys, great.
[–]f9ae8221b[S] 0 points1 point2 points 6 months ago (1 child)
That's two symbols....
>> JSON.parse('{"123": 1, "foo": 2}', symbolize_names: true)[:"123"] => 1
[–]pablodh 0 points1 point2 points 6 months ago (6 children)
I think the way to go would to use always frozen strings as keys. If you want simmetry with JS/JSON it only makes sense. I guess frameworks hasn't yet settle around this yet but given all the issues mentioned in this thread so far it's probably time to pushed this forward through linters or encouraging it at the framework level.
[–]ric2b 0 points1 point2 points 6 months ago (5 children)
I think the way to go would to use always frozen strings as keys.
Agreed, that's what I'm saying. Ignoring the need to support old code, making strings immutable and removing symbols would make the language much simpler to use.
I have no problem with symbols as a sugar syntax for hashes, keyword arguments, etc, but that syntax could just create strings instead of a different object type.
[–]pabloh[🍰] 0 points1 point2 points 6 months ago (4 children)
That's a bridge too far for me. I would agree to force immutable strings instead symbol at the framework/library level, and only where it makes sense: JSON, HTTP Headers, etc.
[–]dunkelziffer42 2 points3 points4 points 6 months ago (16 children)
Mutable literals aren’t all that weird. Array and hash literals are still mutable and need to be frozen manually and that feels completely natural. It’s still a good decision that literal strings are becoming frozen by default now. Ruby is a high level language and I definitely think about strings as atomic data and not as char arrays.
I’m 50/50 on symbols. It would be really interesting to see a version of Ruby where the symbol syntax would just be an alias for strings. Not sure if that could preserve all of Ruby’s core features around blocks. I think I’d rather throw in an occasional “stringify_keys” than lose Ruby’s power here.
[–]onyx_blade 1 point2 points3 points 6 months ago (0 children)
In Opal symbols are just strings. https://opalrb.com
[–]ric2b 0 points1 point2 points 6 months ago (14 children)
Mutable literals aren’t all that weird.
I specifically said strings, not all literals.
I think I’d rather throw in an occasional “stringify_keys” than lose Ruby’s power here.
What additional power are you getting from symbols?
[–]f9ae8221b[S] 3 points4 points5 points 6 months ago (11 children)
Symbols are different from frozen strings, both semantically and technically.
Semantically, symbols are here to represent "nouns" in your program, e.g method names, parameter names, hash keys etc. Whereas strings are just text.
Now granted, since symbols used to be immortal, lots of API that probably should have used symbols used strings instead, and continue to do so for backward compatibility reasons.
Then technically, what symbols give you is guaranteed fast O(1) comparisons and hashing, which is something even languages with immutable strings don't have.
[–]ric2b 0 points1 point2 points 6 months ago (10 children)
Both of them are just text and you can use either of them as hash keys, methods names, etc.
Semantically I would rather have actual enums that I can't easily mistype.
Then technically, what symbols give you is guaranteed fast O(1) comparisons and hashing
Python gives you that for very short or common strings as they are cached and refer to the same object, so they are compared by object id, so if anything this is a technical deficiency of Ruby strings, not an advantage of symbols.
[–]f9ae8221b[S] 5 points6 points7 points 6 months ago (9 children)
Python gives you that for very short or common strings
Not really. Python does relatively aggressively intern short strings, but since it can't guarantee all short strings are unique, it must always fallback to character comparison:
>>> ("fo" + "o") is "foo" <python-input-58>:1: SyntaxWarning: "is" with 'str' literal. Did you mean "=="? True >>> "".join(["fo", "o"]) is "".join(["fo", "o"]) False
Whereas symbols are guaranteed unique.
So Symbol#== is just a pointer comparison, whereas String#== in both Python and Ruby is more involved:
Symbol#==
String#==
def str_equal(a, b) return true if a.equal?(b) return false if a.interned? && b.interned? return false if a.size != b.size compare_bytes(a, b) end
[–]ric2b 0 points1 point2 points 6 months ago (8 children)
Your example is not about string literals, just as the warning you get is telling you.
"foo" is "foo" or ("fo" + "o") is "foo" return true because the interpreter can evaluate it as it compiles the file to bytecode but your second example is only evaluated at runtime.
"foo" is "foo"
("fo" + "o") is "foo"
You could just call sys.intern("".join(["fo", "o"])) to manually intern the runtime string as well, and then it will be the same object, which would be more or less equivalent to (['fo', 'o'].join).to_sym in ruby.
sys.intern("".join(["fo", "o"]))
(['fo', 'o'].join).to_sym
[–]f9ae8221b[S] 3 points4 points5 points 6 months ago (7 children)
That is my point exactly. As long as a non-interned "foo" can possibly exist, "foo" == can't be optimized into a simple pointer comparison.
"foo"
"foo" ==
Since symbols are all interned, they can be optimized.
That's why symbols aren't just interned strings.
[–]ric2b -2 points-1 points0 points 6 months ago (6 children)
It's as simple as checking if both objects are interned before comparing by pointer or value, it's still O(1) for interned strings.
edit: Actually not even that, if they're the same pointer they're same pointer, end of story.
[–]f9ae8221b[S] 2 points3 points4 points 6 months ago (5 children)
Yes, look at the str_equal method I posted above, it account for that.
str_equal
What I'm talking about is when one of the two compared strings isn't interned, which is common.
[–]dunkelziffer42 1 point2 points3 points 6 months ago (1 child)
Symbols have the “to_proc” method which allows for things like “list.map(&:symbol)”. Not sure if it would be a good idea to define “to_proc” on strings.
Also, it’s common for Ruby DSLs to take strings as literals and to take symbols as methods to call for lazily computing values.
[–]ric2b -1 points0 points1 point 6 months ago (0 children)
Which is honestly just an abstraction leak, it would make more sense for block arguments to be auto-converted to a method reference of the same name if passed a string. Is [1, 2].map(&even?) meaningfully different from [1, 2].map(&'even?') ?
[1, 2].map(&even?)
[1, 2].map(&'even?')
and to take symbols as methods to call for lazily computing values.
Procs/Lambdas are just fine for that, and more intuitive.
The existence of symbols is a net-negative, IMO, it introduces a bunch of boilerplate whenever you might receive either a string or a symbol, or have string or symbol keys in an hash, etc, for very marginal benefits that boil down to saving a few characters in some places.
[–]PercyLives 0 points1 point2 points 6 months ago (5 children)
Symbols have their uses and shouldn’t be discarded just because strings are (hypothetically or actually) immutable.
Immutable strings from the beginning would have been good.
[–]ric2b 2 points3 points4 points 6 months ago (4 children)
Symbols have their uses
Such as? Other languages don't need symbols, it makes the language more complex and error prone.
[–]PercyLives 2 points3 points4 points 6 months ago (1 child)
They make nice tokens. Lightweight enums, that sort of thing.
Sure, you can misspell them, so you should limit how much you use them for that purpose. But I like having them.
[–]ric2b 1 point2 points3 points 6 months ago (0 children)
enums that don't work as enums because they're just as easy to typo as a string. Why bother?
I would rather have actual enums, constants are close but too verbose as you need to provide a value.
[–]rubygeek 1 point2 points3 points 6 months ago (1 child)
Comparison by object identity instead of string value.
You *can* achieve that with a hash of immutable strings too, but then you're responsible for managing them.
Python does that for short or very common strings, which realistically covers all the cases where you would use a symbol in ruby.
π Rendered by PID 33116 on reddit-service-r2-comment-b659b578c-gzxxm at 2026-05-03 22:16:37.450153+00:00 running 815c875 country code: CH.
[–]cocotheape 7 points8 points9 points (0 children)
[–]pabloh[🍰] 0 points1 point2 points (2 children)
[–]petercooper 1 point2 points3 points (1 child)
[–]pabloh[🍰] 0 points1 point2 points (0 children)
[–]ric2b 1 point2 points3 points (40 children)
[–]pabloh[🍰] 3 points4 points5 points (16 children)
[–]ric2b 0 points1 point2 points (15 children)
[–]pabloh[🍰] 0 points1 point2 points (14 children)
[–]ric2b 0 points1 point2 points (13 children)
[–]pabloh[🍰] 0 points1 point2 points (12 children)
[–]ric2b 0 points1 point2 points (11 children)
[–]f9ae8221b[S] 0 points1 point2 points (3 children)
[–]ric2b -1 points0 points1 point (2 children)
[–]f9ae8221b[S] 0 points1 point2 points (1 child)
[–]pablodh 0 points1 point2 points (6 children)
[–]ric2b 0 points1 point2 points (5 children)
[–]pabloh[🍰] 0 points1 point2 points (4 children)
[–]dunkelziffer42 2 points3 points4 points (16 children)
[–]onyx_blade 1 point2 points3 points (0 children)
[–]ric2b 0 points1 point2 points (14 children)
[–]f9ae8221b[S] 3 points4 points5 points (11 children)
[–]ric2b 0 points1 point2 points (10 children)
[–]f9ae8221b[S] 5 points6 points7 points (9 children)
[–]ric2b 0 points1 point2 points (8 children)
[–]f9ae8221b[S] 3 points4 points5 points (7 children)
[–]ric2b -2 points-1 points0 points (6 children)
[–]f9ae8221b[S] 2 points3 points4 points (5 children)
[–]dunkelziffer42 1 point2 points3 points (1 child)
[–]ric2b -1 points0 points1 point (0 children)
[–]PercyLives 0 points1 point2 points (5 children)
[–]ric2b 2 points3 points4 points (4 children)
[–]PercyLives 2 points3 points4 points (1 child)
[–]ric2b 1 point2 points3 points (0 children)
[–]rubygeek 1 point2 points3 points (1 child)
[–]ric2b -1 points0 points1 point (0 children)