Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unfreeze an object in Ruby?

Tags:

ruby

In Ruby, there is Object#freeze, which prevents further modifications to the object:

class Kingdom   attr_accessor :weather_conditions end  arendelle = Kingdom.new arendelle.frozen? # => false arendelle.weather_conditions = 'in deep, deep, deep, deep snow' arendelle.freeze arendelle.frozen? # => true arendelle.weather_conditions = 'sun is shining'   # !> RuntimeError: can't modify frozen Kingdom  script = 'Do you want to build a snowman?'.freeze script[/snowman/] = 'castle of ice'   # !> RuntimeError: can't modify frozen String 

However, there is no Object#unfreeze. Is there a way to unfreeze a frozen kingdom?

like image 988
ndnenkov Avatar asked Feb 25 '16 16:02

ndnenkov


People also ask

How do you unfreeze hash?

To thaw your frozen hash browns in the fridge, lay them out on a cookie of baking sheet atop a paper towel overnight. Make sure they thaw for at least 8 hours. Dab them dry before placing in your recipe. If you know you will make a hash brown casserole the next day, defrost your hash browns overnight in the fridge.

What does .freeze do in Ruby?

The freeze method in Ruby is used to ensure that an object cannot be modified. This method is a great way to create immutable objects. Any attempt to modify an object that has called the freeze method will result in the program throwing a runtime error.

What is the use of freeze rails?

By using #freeze, I'm able to create a constant that's actually constant. This time, when I attempt to modify the string, I get a RuntimeError. Here' a real-world example of this in the ActionDispatch codebase. Rails hides sensitive data in logs by replacing it with the text "[FILTERED]".

Are Ruby arrays mutable?

Unlike numbers, booleans, and a few other types, most objects in Ruby are mutable; they are objects of a class that permit changes to the object's state in some way.


2 Answers

Update: As of Ruby 2.7 this no longer works!


Yes and no. There isn't any direct way using the standard API. However, with some understanding of what #freeze? does, you can work around it. Note: everything here is implementation details of MRI's current version and might be subject to change.


Objects in CRuby are stored in a struct RVALUE.
Conveniently, the very first thing in the struct is VALUE flags;.
All Object#freeze does is set a flag, called FL_FREEZE, which is actually equal to RUBY_FL_FREEZE. RUBY_FL_FREEZE will basically be the 11th bit in the flags.
All you have to do to unfreeze the object is unset the 11th bit.

To do that, you could use Fiddle, which is part of the standard library and lets you tinker with the language on C level:

require 'fiddle'  class Object   def unfreeze     Fiddle::Pointer.new(object_id * 2)[1] &= ~(1 << 3)   end end 

Non-immediate value objects in Ruby are stored on address = their object_id * 2. Note that it's important to make the distinction so you would be aware that this wont let you unfreeze Fixnums for example.

Since we want to change the 11th bit, we have to work with the 3th bit of the second byte. Hence we access the second byte with [1].

~(1 << 3) shifts 1 three positions and then inverts the result. This way the only bit which is zero in the mask will be the third one and all other will be ones.

Finally, we just apply the mask with bitwise and (&=).


foo = 'A frozen string'.freeze foo.frozen? # => true foo.unfreeze foo.frozen? # => false foo[/ (?=frozen)/] = 'n un' foo # => 'An unfrozen string' 
like image 159
ndnenkov Avatar answered Sep 29 '22 13:09

ndnenkov


No, according to the documentation for Object#freeze:

There is no way to unfreeze a frozen object.

The frozen state is stored within the object. Calling freeze sets the frozen state and thereby prevents further modification. This includes modifications to the object's frozen state.

Regarding your example, you could assign a new string instead:

script = 'Do you want to build a snowman?' script.freeze  script = script.dup if script.frozen? script[/snowman/] = 'castle of ice' script #=> "Do you want to build a castle of ice?" 

Ruby 2.3 introduced String#+@, so you can write +str instead of str.dup if str.frozen?

like image 31
Stefan Avatar answered Sep 29 '22 12:09

Stefan