Как разморозить объект в 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
. Есть ли способ разморозить замороженное королевство?
Ответы
Ответ 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
Вам просто нужно дублировать и сохранять объект, и он по умолчанию будет разморожен.