initialize_copy in Ruby
Ruby has two methods for creating shallow copies of objects:
Object#clonecopies the object, including its frozen state
Object#dupcopies the object, not including its frozen state
clone is meant to be a more “exact” mechanism for copying objects, whereas
is often implemented by simply creating a new instance of the relevant class with the appropriate
dup copy the tainted state of the object.
the singleton class (if any), whereas
dup does not.
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_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
dup, but it is called by
initialize_dup. Therefore, if you implement your own version of
initialize_dup, it is advisable to call
super to make sure that
initialize_copy is also
Ruby 1.8 behaves in roughly the same way, but it does not have
It would be possible to implement some sort of backport in pure Ruby, but harder to get the semantics to be identical:
Object#clone, the clone is frozen after
- The backport would probably be implemented by calling
superand then calling
initialize_dup(which would be defined in pure Ruby on
Object). But note that the
supercall will result in
initialize_copybeing called already, which is inherently different from the 1.9 implementation where
initialize_dupare in charge of calling
Ouch, head hurts
Yeah, never mind really. I was just curious and thought I’d share my findings.
Thanks a lot!
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.
Thanks, got lead here because it was used in a few vulnerabilities against mruby: https://hackerone.com/repor...
This is still such a great breakdown. I'm using it to refresh my memory without digging into the source today. Thanks!