noname
noname

Reputation: 35

What is the point in Ruby's String#freeze method if one can still reassign a new value to the frozen variable?

I'm trying to understand the use of

# frozen_string_literal: true

in RoR and I understand the freeze method protects the frozen strings from modification up to some extend, but what's the point given that it's as easy to change the string as just reassigning the value? I would expect freeze to block reassignment too if the idea is protecting the variable from modification.

str = "string"
# => "string"
str.freeze
# => "string"
str.frozen?
# => true
str << "fails"
# Traceback (most recent call last):
#         4: from /Users/dev/.rvm/rubies/ruby-2.7.2/bin/irb:23:in `<main>'
#         3: from /Users/dev/.rvm/rubies/ruby-2.7.2/bin/irb:23:in `load'
#         2: from /Users/dev/.rvm/rubies/ruby-2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
#         1: from (irb):4
# FrozenError (can't modify frozen String: "string")
str = "stringfails... but this is fine?!"
# => "stringfails... but this is fine?!"
str
# => "stringfails... but this is fine?!"

I feel like I'm getting confused between freezing the string versus freezing the variable, but a lot of resources mention constants and compare the behaviour to them, so I'm unsure if the idea is to freeze the string assigned to the variable or the variable itself.

Upvotes: 1

Views: 3528

Answers (3)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230531

The general idea behind freezing objects is to make them immutable. This is a valuable trait to have in some situations. Say, you're making an API client, for example.

# frozen_string_literal: true
username = "sergio"
password = "password"
host = "https://example.com/api"
client = MyAPI::Client.new(host, username, password)

Here, once client obtained a reference to a frozen string, it can be sure that this string will never change (from the point of view of this client). So other code can't make host to point to another site or change the password to "bogus" and so on, and have client observe these changes.

With regular, unfrozen strings, you can mutate them in-place in a bunch of ways: gsub!, replace, etc. This affects all references.

This problem is much more pronounced with hashes and arrays, IMHO. In my experience, you mutate them in-place much more often. Like this, for example:

h[:foo] = :bar
ary[0] = 3.14

So if you want to make sure this hash or array never changes*, freeze it.

* caveat: freezing a hash or array only protects its immediate structure. Values in a hash and elements in an array are still mutable, you have to freeze them individually.

Upvotes: 2

user1934428
user1934428

Reputation: 22356

A variable is a pointer. freeze does not make the variable "unmutable" (such a thing would be a constant, not a variable), but disallows modifications on the object it points too. The object becomes a bit similar to a number or symbol, which you can't modify either, i.e. which is always kind of frozen:

a=:sym1
a=:sym2

You can assign something new to a, but you can not change :sym1 itself.

Now to the question what we can use it for, examples on how I use it:

  • If I know that an object should not be modified after a certain point in the lifetime of the program, I freeze it, and if - during a program error - it is being modified, I get an exception, which is useful for debugging.

  • Another example use is with Hash and Set objects. If you use a String as a hash key, or in a Set, Ruby creates a copy of this string, before it is entered in the Hash. This copying is not done if the string is frozen. In our application, we have many hashes, and ran into serious performance problems, because the heap was filled again and again with copies of the strings and garbage collection took a considerable amount of time. There were not so many different strings as such, but they were put into many (often temporary) hashes. As a solution, we implemented a special "Freezer" object, which contains one frozen instance of each string, and use these as Hash keys.

Upvotes: 4

Pascal
Pascal

Reputation: 8656

You can think of the variable like a sign that says: "oh you are looking for the string? it is over there" and it points to the string itself. So you have a "sign" (the variable) and the object itself (the string)

When you freeze, you freeze the object, not the sign that points to the object. So you can not change the frozen string, but you can still make that sign point to something else.

I have called name a variable that points to my object (a String holding my name)

name = "Pascal"

I can make the variable point to another object (your name)

name = "noname" 

The String "Pascal" might still be somewhere in memory but I don't know where. It is lost and will get arbage collected at some point to free the memory.

I don't want anybody to change the value of the string, so i freeze it

name.freeze

But i still can point that variable into another direction

name = "Hondo"

Now there is a frozen String somewhere in memory containg "noname" but again, it is lost since we don't have any sign pointing to it. This frozen string will, if your program runs long enough), be garbage collected as well.

We can also have multiple variables pointing to the same object:

variable_a = "something"
=> "something"
variable_b = variable_a
=> "something"
variable_a.object_id
=> 70104549867280
variable_b.object_id
=> 70104549867280

object_id gives you an identifier of the object the variable is pointing to. It isthe same for vairable_a and variable_b so they are pointing to the same object.

Upvotes: 1

Related Questions