How to unfreeze an object in Ruby?

后端 未结 4 1229
生来不讨喜
生来不讨喜 2020-12-13 17:50

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

class Kingdom
  attr_accessor :weather_conditions
end

arendelle = Kingd         


        
相关标签:
4条回答
  • 2020-12-13 18:14

    As noted above copying the variable back into itself also effectively unfreezes the variable.

    As noted this can be done using the .dup method:

    var1 = var1.dup

    This can also be achieved using:

    var1 = Marshal.load(Marshal.dump(var1))

    I have been using Marshal.load(Marshal.dump( ... )

    I have not used .dup and only learned about it through this post.

    I do not know what if any differences there are between Marshal.load(Marshal.dump( ... )

    If they do the same thing or .dup is more powerful, then stylistically I like .dup better. .dup states what to do -- copy this thing, but it does not say how to do it, whereas Marshal.load(Marshal.dump( ... ) is not only excessively verbose, but states how to do the duplication -- I am not a fan of specifying the HOW part if the HOW part is irrelevant to me. I want to duplicate the value of the variable, I do not care how.

    0 讨论(0)
  • 2020-12-13 18:20
    frozen_object = %w[hello world].freeze
    frozen_object.concat(['and universe']) # FrozenError (can't modify frozen Array)
    frozen_object.dup.concat(['and universe']) # ['hello', 'world', 'and universe']
    
    0 讨论(0)
  • 2020-12-13 18:21

    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'
    
    0 讨论(0)
  • 2020-12-13 18:30

    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?

    0 讨论(0)
提交回复
热议问题