Feature #17104
closedDo not freeze interpolated strings when using frozen-string-literal
Description
I think the point of frozen string literals is to avoid needless allocations. Interpolated strings are allocated each time, so freezing them appears pointless.
#frozen_string_literal: true
def foo(str)
"#{str}"
end
fr1 = 'a'
fr2 = 'a'
fr1_1 = foo(fr1)
fr2_1 = foo(fr2)
puts fr1.__id__, fr2.__id__, fr1_1.__id__, fr2_1.__id__
puts fr1_1 << 'b'
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
- Status changed from Open to Closed
Other people have felt the same way, including me (see https://bugs.ruby-lang.org/issues/11473#note-7 and https://bugs.ruby-lang.org/issues/8976#note-67). However, @matz (Yukihiro Matsumoto) decided during the November 2015 developer meeting that they should be frozen for simplicity, see https://docs.google.com/document/d/1D0Eo5N7NE_unIySOKG9lVj_eyXf66BQPM4PKp7NvMyQ/pub
Updated by bughit (bug hit) over 4 years ago
If you want to get a mutable string from an interpolated literal +"_#{method1}_"
, 2 allocation will be done instead of 1, if it weren't pointlessly frozen.
In this case a feature designed to reduce allocations is producing more allocations. Behavior that's counter-intuitive and illogical and acting counter to its intent, is not simple.
This happens to be something that can be changed without breaking anything. Can it get a second look?
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
If you would like the behavior changed, you should file a feature request for it, and add it to the agenda of a future developer meeting.
Updated by Eregon (Benoit Daloze) over 4 years ago
FWIW I'd be +1 to make interpolated Strings non-frozen.
It's BTW still the behavior in TruffleRuby to this day, since it seems to cause no incompatibility and is convenient for writing the core library with Ruby code.
Updated by bughit (bug hit) over 4 years ago
Can't we just treat this as I feature request? The reasons are, it will reduce allocations, be more logical, less surprising and produce simpler code (when a mutable string is needed and you don't want extra allocations)
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
- Tracker changed from Misc to Feature
- Subject changed from Why are interpolated string literals frozen? to Do not freeze interpolated strings when using frozen-string-literal
- Status changed from Closed to Open
We can treat this as a feature request. I changed it from Misc to Feature and modified the Subject to be the feature you are requesting as opposed to a question. The issue for the next developer meeting is at https://bugs.ruby-lang.org/issues/17041 if you want to add it to the agenda.
Updated by Dan0042 (Daniel DeLorme) over 4 years ago
+1
A while ago I opened ticket #16047 about the same thing.
Seems a lot of people don't like the current behavior so much.
Updated by byroot (Jean Boussier) over 4 years ago
If you want to get a mutable string from an interpolated literal +"#{method1}", 2 allocation will be done instead of 1
Maybe the parser could understand +"#{}"
and avoid that second allocation? The same way it understand "".freeze
.
Because I understand the consistency argument. "All string literals are frozen" is much easier to wrap your head around than "All string literals are frozen except the ones that are interpolated".
Updated by Eregon (Benoit Daloze) over 4 years ago
byroot (Jean Boussier) wrote in #note-8:
Because I understand the consistency argument. "All string literals are frozen" is much easier to wrap your head around than "All string literals are frozen except the ones that are interpolated".
I'd argue "a#{2}c"
is not a string literal ("a2c"
is a string literal).
It's actually syntactic sugar for mutableEmptyStringOfSourceEncoding << "a" << 2.to_s << "c"
.
(+ .freeze
the result in current semantics which feels unneeded)
Same as 42
is an integer literal, but 41 + 1
is not an integer literal.
Updated by byroot (Jean Boussier) over 4 years ago
I'd argue "a#{2}c" is not a string literal ("a2c" is a string literal).
I understand your point of view. However in my view what defines a literal, is the use of a specific syntax, so ""
in this case.
Same for hashes or arrays, [1 + 2]
is a literal (to me), it might not be a "static" literal, but it is a literal nonetheless.
Updated by bughit (bug hit) over 4 years ago
However in my view what defines a literal, is the use of a specific syntax, so ""
There is only one reason for freezing literals, to be able to intern them and reduce allocation. In fact the feature is poorly named, after the consequence(freezing), not the cause (interning).
Freezing strings that are not interned is pointless and counterproductive (it leads to more allocation in the name of less).
A user who does not understand why literals are frozen is unlikely to even notice that interpolated ones are not. And if he does and wonders why, he will, if persistent, arrive at the reason (interning) and be better off for it. The foolish, in this case, consistency in the name of "simplicity" helps no one.
Updated by duerst (Martin Dürst) over 4 years ago
bughit (bug hit) wrote in #note-11:
However in my view what defines a literal, is the use of a specific syntax, so ""
There is only one reason for freezing literals, to be able to intern them and reduce allocation. In fact the feature is poorly named, after the consequence(freezing), not the cause (interning).
My understanding is that another reason is avoidance of alias effects. It's easy to write code that when cut down to the essential, does this:
a = b = "My string"
a.gsub!(/My/, 'Your')
and expects b
to still be "My string". Freezing makes sure this throws an error.
(This is not an argument for or againts freezing interpolated strings.)
Updated by bughit (bug hit) over 4 years ago
another reason is avoidance of alias effects
What you've shown is not another reason for freezing.
a = b = "My string"
both a and b refer to the same string object regardless of interning/freezing
there's no expectation that mutating it via a
will not affect b
the interning scenario is:
a = "My string"
b = "My string"
a.gsub!(/My/, 'Your')
here there's an appearance of 2 string objects but when they are interned, there's only one, so mutation can not be allowed. As I said, interning is the feature, and it requires freezing.
Updated by akr (Akira Tanaka) about 4 years ago
Non-freezing interpolated strings is good thing to reduce allocations.
I think it is possible if most developers understands difference of interpolated and non-interpolated strings.
Updated by matz (Yukihiro Matsumoto) about 4 years ago
OK. Persuaded. Make them unfrozen.
Matz.
Updated by Eregon (Benoit Daloze) about 4 years ago
- Assignee set to Eregon (Benoit Daloze)
I'll try to make a PR for this change: https://github.com/ruby/ruby/pull/3488
Updated by mame (Yusuke Endoh) about 4 years ago
Eregon (Benoit Daloze) wrote in #note-17:
I'll try to make a PR for this change: https://github.com/ruby/ruby/pull/3488
Thanks, I've given it a try.
I found "foo#{ "foo" }"
frozen because it is optimized to "foofoo"
at the parser. What do you think?
$ ./miniruby --enable-frozen-string-literal -e 'p "foo#{ "foo" }".frozen?'
true
Updated by Eregon (Benoit Daloze) about 4 years ago
mame (Yusuke Endoh) wrote in #note-18:
I found
"foo#{ "foo" }"
frozen because it is optimized to"foofoo"
at the parser. What do you think?
I guess that's semantically correct (besides the frozen status), since interpolation does not need to call #to_s
for a String.
Since such code is very unlikely to appear in real code, I think it ultimately does not matter too much.
But if we can easily remove that optimization in the parser, I think it would be better, because this is an inconsistency (it makes it harder to reason about Ruby semantics & it breaks referential transparency) and optimizing "foo#{ "foo" }"
seems to have no use in practice.
Could you point me to where the optimization is done in the parser if you found it? :)
Updated by ko1 (Koichi Sasada) about 4 years ago
I found that freezing interpolated strings are help for Ractor programming with constants.
class C
i = 10
STR = "foo#{i}"
end
Updated by Eregon (Benoit Daloze) about 4 years ago
ko1 (Koichi Sasada) wrote in #note-21:
I found that freezing interpolated strings are help for Ractor programming with constants.
Right, anything deeply frozen is helpful for Ractor.
But interpolated Strings are probably not that common.
I see an interpolated String much like an Array literals, and those are not frozen without an explicit .freeze
.
Related comment: https://bugs.ruby-lang.org/issues/17100#note-23
Updated by Eregon (Benoit Daloze) about 4 years ago
- Status changed from Open to Closed
Applied in changeset git|9b535f3ff7c2f48e34dd44564df7adc723b81276.
Interpolated strings are no longer frozen with frozen-string-literal: true
- Remove freezestring instruction since this was the only usage for it.
- [Feature #17104]
Updated by Eregon (Benoit Daloze) about 4 years ago
I merged https://github.com/ruby/ruby/pull/3488 so it's in time for preview1.
@nobu (Nobuyoshi Nakada) Could you decide if you think https://github.com/nobu/ruby/tree/unfrozen-literal is worth it?
Updated by marcandre (Marc-Andre Lafortune) almost 4 years ago
- Is duplicate of Misc #16047: Reconsider impact of frozen_string_literal on dynamic strings added