initialize_clone
, initialize_dup
and initialize_copy
in Ruby
Ruby has two methods for creating shallow copies of objects:
-
Object#clone
copies the object, including its frozen state -
Object#dup
copies the object, not including its frozen state
In general clone
is meant to be a more “exact” mechanism for copying objects, whereas dup
is often implemented by simply creating a new instance of the relevant class with the appropriate
parameters. Both clone
and dup
copy the tainted state of the object. clone
copies
the singleton class (if any), whereas dup
does not.
Initialising copies
Sometimes it is useful to be able to specify some initialisation code that should be run when an object is copied. For example, suppose an object tracks its own internal state in some way, you may wish to reset this state when the object is copied.
Ruby 1.9 has 3 methods to help you do this: initialize_clone
,
initialize_dup
and initialize_copy
. At present, there is no documentation (that I can find),
so I had to do a bit of digging through C code to work out the exact behaviour.
The implementation is expressed by the following psuedo-code:
class Object
def clone
clone = self.class.allocate
clone.copy_instance_variables(self)
clone.copy_singleton_class(self)
clone.initialize_clone(self)
clone.freeze if frozen?
clone
end
def dup
dup = self.class.allocate
dup.copy_instance_variables(self)
dup.initialize_dup(self)
dup
end
def initialize_clone(other)
initialize_copy(other)
end
def initialize_dup(other)
initialize_copy(other)
end
def initialize_copy(other)
# some internal stuff (don't worry)
end
end
initialize_copy
runs for both clone
and dup
, but it is called by initialize_clone
and initialize_dup
. Therefore, if you implement your own version of initialize_clone
or
initialize_dup
, it is advisable to call super
to make sure that initialize_copy
is also
called.
Ruby 1.8
Ruby 1.8 behaves in roughly the same way, but it does not have initialize_dup
or
initialize_clone
built-in.
It would be possible to implement some sort of backport in pure Ruby, but harder to get the semantics to be identical:
- In
Object#clone
, the clone is frozen afterinitialize_clone
is called - The backport would probably be implemented by calling
super
and then callinginitialize_clone
orinitialize_dup
(which would be defined in pure Ruby onObject
). But note that thesuper
call will result ininitialize_copy
being called already, which is inherently different from the 1.9 implementation whereinitialize_clone
andinitialize_dup
are in charge of callinginitialize_copy
.
Ouch, head hurts
Yeah, never mind really. I was just curious and thought I’d share my findings.
Comments
I'd love to hear from you here instead of on corporate social media platforms! You can also contact me privately.
MarkDBlackwell
Thanks a lot!
tombeynon
Thanks, this was very helpful. I'm surprised there's still such little documentation on this, especially since it's mentioned in the Ruby docs under #dup and #clone.
Dorian
Thanks, got lead here because it was used in a few vulnerabilities against mruby: https://hackerone.com/repor...
Avdi Grimm
This is still such a great breakdown. I'm using it to refresh my memory without digging into the source today. Thanks!
Add your comment