Подтвердить что ты не робот

Как разморозить объект в Ruby?

В Ruby существует Object#freeze, что предотвращает дальнейшие модификации объекта:

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

Однако нет Object#unfreeze. Есть ли способ разморозить замороженное королевство?

4b9b3361

Ответ 1

Да и нет. Нет прямого способа использования стандартного API. Однако, с некоторым пониманием того, что делает #freeze?, вы можете обойти его. Примечание. Здесь все детали реализации текущей версии MRI и могут быть изменены.


Объекты в CRuby хранятся в структуре RVALUE.
Удобно, что самое первое в структуре VALUE flags;.
Все Object#freeze устанавливает флаг, называемый FL_FREEZE, который на самом деле равен RUBY_FL_FREEZE. RUBY_FL_FREEZE будет в основном 11-й бит в флагах.
Все, что вам нужно сделать, чтобы разморозить объект, отменяет 11-й бит.

Для этого вы можете использовать Fiddle, который является частью стандартной библиотеки и позволяет вам возиться с языком на уровне C

require 'fiddle'

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

Не < объекты немедленного значения в Ruby хранятся по адресу = их object_id * 2. Обратите внимание, что важно сделать различие, чтобы вы знали, что это не позволит вам разморозить Fixnum например.

Поскольку мы хотим изменить 11-й бит, нам нужно работать с 3-м битом второго байта. Следовательно, мы получаем второй байт с [1].

~(1 << 3) сдвигает 1 три позиции и затем инвертирует результат. Таким образом, единственный бит, который равен нулю в маске, будет третьим, а все остальные будут.

Наконец, мы просто применяем маску с побитовым и (&=).


foo = 'A frozen string'.freeze
foo.frozen? # => true
foo.unfreeze
foo.frozen? # => false
foo[/ (?=frozen)/] = 'n un'
foo # => 'An unfrozen string'

Ответ 2

Нет, согласно документации для Object#freeze:

Невозможно разморозить замороженный объект.

Замороженное состояние сохраняется внутри объекта. Вызов freeze устанавливает замороженное состояние и тем самым предотвращает дальнейшую модификацию. Это включает в себя изменения в замороженном состоянии объекта.

Что касается вашего примера, вы можете назначить вместо него новую строку:

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 представил String#[email protected], поэтому вы можете написать +str вместо str.dup if str.frozen?

Ответ 3

Как отмечено выше, копирование переменной обратно в себя также эффективно размораживает переменную.

Как уже отмечалось, это можно сделать с помощью метода .dup:

var1 = var1.dup

Это также может быть достигнуто с помощью:

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

Я использовал Marshal.load(Marshal.dump( ... )

Я не использовал .dup и узнал об этом только через этот пост.

Я не знаю, что, если между Marshal.load(Marshal.dump( ... )

существуют различия,

Если они делают то же самое или .dup является более мощным, то стилистически мне нравится .dup лучше. .dup указывает, что делать - копируйте эту вещь, но она не говорит, как это сделать, тогда как Marshal.load(Marshal.dump( ... ) не только чрезмерно многословна, но и указывает, как сделать дублирование - я не поклонник указания части HOW, если часть HOW не имеет отношения ко мне. Я хочу дублировать значение переменной, мне все равно, как.

Ответ 4

Простое размораживание объекта в рубине.

Вот пример. Допустим

name1="Praveen".freeze
name1.frozen? #true
name1 = name1.dup
name1.frozen #false

Вам просто нужно дублировать и сохранять объект, и он по умолчанию будет разморожен.