Why frozen string literals ?
What is reason. It seems it makes coding more difficult.
I often do something like …
puts 'Value is: '<<value.inspect
But if the file has this magic comment at the top …
# frozen_string_literal: true
… this can produce frozen string errors.
I use the String#<< append method so as to avoid the creation of a 3rd String object that String#+ creates.
The restrictions that frozen string literals impose (to me) have the flavor of the hoops we had to jump through with Strings in the Ada programming language. (Perhaps one of the reasons it was abandoned by the DoD as the required language for defense projects.)
Frozen strings (and other objects) may result in time and/or memory optimizations for some applications. Whether the improvements are significant is another matter.
Re the magic comment, a lot of Ruby code uses it, and it can be set globally via RUBYOPT
. I believe Rails 5 and 6 both use it, and, given how large Rails is, I doubt they would have added it without a good reason.
But, SU plugins aren’t as complex as some Rails apps…
At one point, it was going to be enforced with the first release of Ruby 3.0. I think that change has been put off.
Regardless, I use it out of habit. As to your specific example, you can use string interpolation. It’s only one more character…
1 Like
Oh, so …
puts "Value is: #{value.inspect}"
… will not raise FrozenError
?
Correct.
General idea is that anything that is frozen cannot be modified, but it can be given a whole new value.
One also sees constants being frozen. For instance, a constant can be assigned an array value, but the array can be modified. So freezing the array disallows that…
I find that if I want to really freeze constants, I put them in a submodule, freeze it, and mix that into the extension module. (Whoops, incorrect, see below.) Attempts to reassign constants then produce “Cannot modify frozen module” errors.
You might check that. I think you need to refer the constants by their namespace (the frozen module) rather than mixing them in.
I believe mixing them in creates a situation where they’re considered ‘ancestors’, and they can then be redefined in the module they were mixed into?
Of course. Anything inherited can be overridden.
But the constants within the frozen submodule cannot be changed themselves.
TRUE.
“My bad” with the “include
” suggestion.
Technically, you don’t give the frozen thing a new value, but give the variable referencing the frozen thing a new value. Frozen is a property of the object, not variable.
Yes, it is better to describe it that way. Variables are pointers or references to objects, and one can change the pointer/reference. Frozen only affects whether an object is mutable.
Also, to make it clear, freezing strings is an optimization, not something done to ‘lock’ values. ‘Locking’ variables/constants/etc is another topic, and in Ruby, really can’t be done without a lot of effort. ‘Locking’ can be done to avoid coding mistakes…
1 Like
Freezing a string can to some extents be used to lock the value. Consider this example.
MY_CONSTANT = "Hello"
def my_method(string)
string << " World!"
puts string
end
my_method(MY_CONSTANT)
Less experienced developers, or developers without Ruby experience, could accidentally think the string is passed as a value, not a reference. You could also pass an object you think isn’t used anywhere else and assume is fine to mutate, but in fact use the reference elsewhere where you expected the original value (I’ve done this a lot of times on Arrays myself).
Freezing an object doesn’t protect the reference from being completely re-assigned, but I think that is less likely to happen by accident. In the case of a constant, re-assigning also produces a warning, while mutating doesn’t.
It is my understanding that freeze allows Ruby to lock a String to the original text in the source code instead of creating a separate buffer as needed for a mutable object. So, it is an optimization.
In the case of doing it for all strings it is indeed optimization. When it comes to manually freezing individual strings I can’t say to what degree it is optimization and to what degree avoid doing mistakes.
@MSP_Greg, is there any difference with the magic comment between how Ruby treats single and double quoted string literals ?
Well if I can’t do …
puts 'Value is: '<<value.inspect
… then (IMO) interpolation …
puts "Value is: #{value.inspect}"
… is better than …
puts 'Value is: %s' % value.inspect
… and even …
puts 'Value is: ' + value.inspect
… and always better than this …
puts String.new('Value is: ')<<value.inspect
1 Like
Interpolation is also recommended by most style guides I’ve seen.
1 Like
Yes. Three ways to make an unfrozen string
s = +'abcd'
s = 'abcd'.dup
s = String.new 'abcd'
So, you could use
puts 'Value is: '.dup<<value.inspect
Note that String.new
with no argument returns an ASCII-8BIT string. The +'abcd'
syntax is new, can’t recall which versions it works with.
But this would defeat the reason I always used <<
, which was to avoid the creation of a third String object as +
does.
I think that I must favor the least weird and most readable paradigm.
In this case the readability of s1+s2
wins out over s1.dup<<s2
.
But I think I’ll still favor interpolation from now on.
1 Like
I’d be careful about second guessing how compilers/interpreters work without benchmarks.
Looking at things with ObjectSpace.#count_objects
, there doesn’t seem to be any difference between three ways of creating an unfrozen string from a literal string:
String.new 'Test'
'Test'.dup
+'Test'
+'Test'
seemed to be the fastest.
Creating a unfrozen string from a frozen string assigned to a variable also showed similar timing. When using the +
operator, one may need to surround it with parens (like (+str)
). It is only available in Ruby 2.3 and later.
Which means it won’t work in SU2018 or earlier.
It is also cryptic IMO. Not what I like for programming languages. I’ll be avoiding it myself.
.dup
is “plain as mud”.
But the list above is in the order I prefer. After appending and interpolation, the format strings with replacement parameters are the next most paradigm I use.