close

Project

General

Profile

Actions

Feature #17104

closed
Image Image

Do not freeze interpolated strings when using frozen-string-literal

Feature #17104: Do not freeze interpolated strings when using frozen-string-literal

Added by bughit (bug hit) over 5 years ago. Updated over 5 years ago.

Status:
Closed
Target version:
[ruby-core:99485]

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'

Related issues 1 (0 open1 closed)

Is duplicate of Ruby - Misc #16047: Reconsider impact of frozen_string_literal on dynamic stringsClosedmatz (Yukihiro Matsumoto)Actions

Image Updated by jeremyevans0 (Jeremy Evans) over 5 years ago Actions #1 [ruby-core:99487]

  • 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

Image Updated by bughit (bug hit) over 5 years ago Actions #2 [ruby-core:99488]

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?

Image Updated by jeremyevans0 (Jeremy Evans) over 5 years ago Actions #3 [ruby-core:99489]

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.

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #4 [ruby-core:99490]

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.

Image Updated by bughit (bug hit) over 5 years ago Actions #5 [ruby-core:99491]

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)

Image Updated by jeremyevans0 (Jeremy Evans) over 5 years ago Actions #6 [ruby-core:99492]

  • 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.

Image Updated by Dan0042 (Daniel DeLorme) over 5 years ago Actions #7 [ruby-core:99493]

+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.

Image Updated by byroot (Jean Boussier) over 5 years ago Actions #8 [ruby-core:99496]

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".

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #9 [ruby-core:99497]

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.

Image Updated by byroot (Jean Boussier) over 5 years ago Actions #10 [ruby-core:99501]

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.

Image Updated by bughit (bug hit) over 5 years ago Actions #11 [ruby-core:99546]

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.

Image Updated by duerst (Martin Dürst) over 5 years ago Actions #12 [ruby-core:99551]

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.)

Image Updated by bughit (bug hit) over 5 years ago Actions #13 [ruby-core:99560]

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.

Image Updated by sawa (Tsuyoshi Sawada) over 5 years ago Actions #14

  • Description updated (diff)

Image Updated by akr (Akira Tanaka) over 5 years ago Actions #15 [ruby-core:99699]

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.

Image Updated by matz (Yukihiro Matsumoto) over 5 years ago Actions #16 [ruby-core:99793]

OK. Persuaded. Make them unfrozen.

Matz.

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #17 [ruby-core:99802]

  • Assignee set to Eregon (Benoit Daloze)

I'll try to make a PR for this change: https://github.com/ruby/ruby/pull/3488

Image Updated by mame (Yusuke Endoh) over 5 years ago Actions #18 [ruby-core:99817]

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

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #19 [ruby-core:99822]

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? :)

Image Updated by ko1 (Koichi Sasada) over 5 years ago Actions #21 [ruby-core:99858]

I found that freezing interpolated strings are help for Ractor programming with constants.

class C
  i = 10
  STR = "foo#{i}"
end

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #22 [ruby-core:99877]

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

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #23

  • 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]

Image Updated by Eregon (Benoit Daloze) over 5 years ago Actions #25

  • Target version set to 3.0

Image Updated by marcandre (Marc-Andre Lafortune) over 5 years ago Actions #26

  • Is duplicate of Misc #16047: Reconsider impact of frozen_string_literal on dynamic strings added
Actions

Also available in: PDF Atom