DEV Community

Lucian Ghinda
Lucian Ghinda

Posted on • Originally published at allaboutcoding.ghinda.com

Alternative ways to freeze a string in Ruby

If you want to freeze strings in Ruby there are at least two ways to do this:

1) Adding the magic comment at the beginning of the file

# frozen_string_literal: true
Enter fullscreen mode Exit fullscreen mode

2) Calling .freeze on the string that you want to freeze

a = "this is a frozen string".freeze
puts a.frozen? # will return true 

b = "this is a frozen string".freeze
puts b.frozen? # will return true 

puts a.equal?(b) # will return true
puts a.object_id == b.object_id # true
Enter fullscreen mode Exit fullscreen mode

As you notice a and b seem to be the same object instance.
No new objects are instantiated.

The same happens for symbols:

s = :a_new_symbol_open
puts s.frozen? # will, of course return true

m = :a_new_symbol_open
puts m.frozen? # will, of course return true

puts s.object_id == m.object_id # will return true
Enter fullscreen mode Exit fullscreen mode

Alternative ways to freeze a String

Enter a kind of strange method that can be applied on Strings: -

You might see code that looks like this:

status = -"global.pending"
Enter fullscreen mode Exit fullscreen mode

String#-@ and String#+@ methods

Let's explore how these methods work.

Using a string literal will create a new String object every time an assignment takes place. Notice in the following example that the object_id is different between str1 and str2:

str1 = "Normal string"
puts "#{str1.object_id}, #{str1.frozen?}" # 60, false

str2 = "Normal string"
puts "#{str2.object_id}, #{str2.frozen?}" # 80, false

puts str1.object_id == str2.object_id # false
Enter fullscreen mode Exit fullscreen mode

What does using - on a String do?

First, here is the definition of String#-

Returns a frozen, possibly pre-existing copy of the string

The returned String will be deduplicated if it has no instance variables.

Notice in the following example that the strings are frozen and return the same object_id:

str3 = -"Normal string"
puts "#{str3.object_id}, #{str3.frozen?}" # 100, true

# Here for example, it will return the same object id
str4 = -"Normal string"
puts "#{str4.object_id}, #{str4.frozen?}" # 100, true

puts str3.object_id == str4.object_id # true
Enter fullscreen mode Exit fullscreen mode

Thus we are not only making the string close to modifications but we are also re-using the same object.

How to unfreeze such string?

There is a counter-part method on String: +

It does the following:

Returns a frozen, possibly pre-existing copy of the string.

The returned String will be deduplicated as long as it does not have any instance variables set on it.

frozen_string = -"This is a frozen string"
begin
  frozen_string << "and it cannot be modified"
rescue FrozenError => e
  puts e # can't modify frozen String: "This is a frozen string"
end 

puts "#{frozen_string.object_id}, #{frozen_string.frozen?}" # 120, true

str5 = +frozen_string 
puts "#{str5.object_id}, #{str5.frozen?}"  # 140, false

str5 << " and it can be modified" # This is a frozen string and it can be modified
puts str5
Enter fullscreen mode Exit fullscreen mode

Exploring some interesting cases

Hashes with String keys in Ruby are freezing the keys. But are the keys the same object? What about values?

hash1 = { "Key" => "Value" }
key1 = hash1.keys[0]
value1 = hash1.values[0]
puts "H1: #{key1.object_id}, #{value1.object_id}" # 160, 180
puts "H1: #{key1.frozen?}, #{value1.frozen?}" # true, false


hash2 = { -"Key" => "Value" }
key2 = hash2.keys[0]
value2 = hash2.values[0]
puts "H2: #{key2.object_id}, #{value2.object_id}" # 160, 200
puts "H2: #{key2.frozen?}, #{value2.frozen?}" # true, false

puts key1.equal?(key2) # true
puts key1.object_id == key2.object_id # true
Enter fullscreen mode Exit fullscreen mode

As you can notice, the key has the same object_id. Thus, it is the same object instance. But values have different object_id, so they are not the same object.

puts value1.equal?(value2) # false
puts value1.object_id == value2.object_id # false
Enter fullscreen mode Exit fullscreen mode

So if you want to make also the value the same object as it is the same string literal:

hash3 = { "Key" => -"Value" }
key3 = hash2.keys[0]
value3 = hash2.values[0]
puts "H3: #{key3.object_id}, #{value3.object_id}" # 160, 200
puts "H3: #{key3.frozen?}, #{value3.frozen?}" # true, true

hash4 = { "Key" => -"Value" }
key4 = hash2.keys[0]
value4 = hash2.values[0]
puts "H4: #{key4.object_id}, #{value4.object_id}" # 160, 200
puts "H4: #{key4.frozen?}, #{value4.frozen?}" # true, true

puts value3.equal?(value4) # true
puts value3.object_id == value4.object_id # true
Enter fullscreen mode Exit fullscreen mode

Some examples of using the - on strings

First when possible, use symbols instead of strings.

Of course, symbols have a bit more restrictive rules to write, so it might be that for clarity or other reasons you cannot/don't want to use symbols.

So use - on strings that will probably not have variations, not they will be modified

Examples:

1) When writing a custom SQL that might be re-used in some other places

SolarSystem.planets.order(-"orbit = 'circular' ASC, name ASC")
Enter fullscreen mode Exit fullscreen mode

2) When calling an external API where you define the base URL or the path:

base = -"http://example.com"
url = URI.join(base, -"/foo")
Enter fullscreen mode Exit fullscreen mode

3) When adding, for example headers:

header = { "Content-Type" => -"application/x-www-form-urlencoded" }
Enter fullscreen mode Exit fullscreen mode

4) When you want to log some structured short information:

Rails.logger(-"job.start.highpriority")
Enter fullscreen mode Exit fullscreen mode

If you like this type of content, follow me on Twitter lucianghinda where I share/retweet mostly Ruby and Rails content.

I also publish a newsletter with Ruby and Rails fresh content at newsletter.shortruby.com — in case you want to stay up to date with what is happening in Ruby and Rails world.

Top comments (0)