Ruby 3.0 Interpolated Strings Are No Longer Frozen
September 16, 2020
In Ruby, frozen_string_literal: true
makes all string literals frozen by default and it helps in reducing needless memory allocations by not creating a new allocation each time a string is redefined.
In Ruby 2.7.1 and earlier versions, interpolated strings were also considered to be frozen when frozen_string_literal: true
is used.
# frozen_string_literal: true
foo = "Foo" # frozen
bar = "Bar" # frozen
"#{foo} #{bar}" # frozen
This was intentionally done for simplicity that all strings with ""
or ''
are frozen by default when frozen_string_literal: true
is present.
But an interpolated string is not actually a string literal but rather it can be called as a dynamic string.
Let's see this example:
# Ruby 2.7.1
# frozen_string_literal: true
def full_name(first_name, last_name)
"Mr. #{first_name} #{last_name}"
end
fname = "Dummy"
lname = "Rik"
name = full_name(fname, lname)
puts fname.frozen?
#=> true
puts lname.frozen?
#=> true
puts name.frozen?
#=> true
puts name.gsub!(/Mr/, "Mrs")
#=> `gsub!': can't modify frozen String: "Mr. Dummy Rik" (FrozenError)
As we can see above, name
is an interpolated string i.e dynamic string.
full_name
method by default returns a frozen string as Ruby makes interpolated strings frozen by default when frozen_string_literal: true
is used.
Let's fix the above FrozenError
:
# Ruby 2.7.1
# frozen_string_literal: true
def full_name(first_name, last_name)
interpolated_string = "Mr. #{first_name} #{last_name}"
puts "interpolated_string frozen? : #{interpolated_string.frozen?}"
puts "interpolated_string object_id : #{interpolated_string.object_id}"
dup_string = interpolated_string.dup
puts "dup_string frozen? : #{dup_string.frozen?}"
puts "dup_string object_id : #{dup_string.object_id}"
dup_string
end
fname = "Dummy"
lname = "Rik"
name = full_name(fname, lname)
puts name.gsub!(/Mr/, "Mrs")
#=> interpolated_string frozen? : true
#=> interpolated_string object_id : 60
#=> dup_string frozen? : false
#=> dup_string object_id : 80
#=> Mrs. Dummy Rik
We have used dup
to return a non freezing string, so that we can use gsub!
on it.
But we can see above (interpolated_string object_id : 60, dup_string object_id : 80), we have created two memory allocations unnecessarily.
As said above, we use frozen_string_literal: true
to reduce memory allocations but here we are creating extra memory allocation.
Ruby core team has accepted a proposal to not to freeze interpolated strings when frozen_string_literal: true
is used and this has been merged to Ruby 3.0 .
Let's for the final time see how our example will shape with Ruby 3.0 :
# Ruby 3.0
# frozen_string_literal: true
def full_name(first_name, last_name)
"Mr. #{first_name} #{last_name}"
end
fname = "Dummy"
lname = "Rik"
name = full_name(fname, lname)
puts name.gsub!(/Mr/, "Mrs")
#=> Mrs. Dummy Rik
info@scriptday.com