What's the difference between Ruby's dup and clone methods?
The Ruby docs for dup
say:
In general,
clone
anddup
may have different semantics in descendent classes. Whileclone
is used to duplicate an object, including its internal state,dup
typically uses the class of the descendent object to create the new instance.
But when I do some test I found they are actually the same:
class Test
attr_accessor :x
end
x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7
So what are the differences between the two methods?
Subclasses may override these methods to provide different semantics. In Object
itself, there are two key differences.
First, clone
copies the singleton class, while dup
does not.
o = Object.new
def o.foo
42
end
o.dup.foo # raises NoMethodError
o.clone.foo # returns 42
Second, clone
preserves the frozen state, while dup
does not.
class Foo
attr_accessor :bar
end
o = Foo.new
o.freeze
o.dup.bar = 10 # succeeds
o.clone.bar = 10 # raises RuntimeError
The Rubinius implementation for these methods is often my source for answers to these questions, since it is quite clear, and a fairly compliant Ruby implementation.
When dealing with ActiveRecord there's a significant difference too:
dup
creates a new object without its id being set, so you can save a new object to the database by hitting .save
category2 = category.dup
#=> #<Category id: nil, name: "Favorites">
clone
creates a new object with the same id, so all the changes made to that new object will overwrite the original record if hitting .save
category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
One difference is with frozen objects. The clone
of a frozen object is also frozen (whereas a dup
of a frozen object isn't).
class Test
attr_accessor :x
end
x = Test.new
x.x = 7
x.freeze
y = x.dup
z = x.clone
y.x = 5 => 5
z.x = 5 => TypeError: can't modify frozen object
Another difference is with singleton methods. Same story here, dup
doesn't copy those, but clone
does.
def x.cool_method
puts "Goodbye Space!"
end
y = x.dup
z = x.clone
y.cool_method => NoMethodError: undefined method `cool_method'
z.cool_method => Goodbye Space!
The newer doc includes a good example:
class Klass
attr_accessor :str
end
module Foo
def foo; 'foo'; end
end
s1 = Klass.new #=> #<Klass:0x401b3a38>
s1.extend(Foo) #=> #<Klass:0x401b3a38>
s1.foo #=> "foo"
s2 = s1.clone #=> #<Klass:0x401b3a38>
s2.foo #=> "foo"
s3 = s1.dup #=> #<Klass:0x401b3a38>
s3.foo #=> NoMethodError: undefined method `foo' for #<Klass:0x401b3a38>
Both are nearly identical but clone does one more thing than dup. In clone, the frozen state of the object is also copied. In dup, it’ll always be thawed.
f = 'Frozen'.freeze
=> "Frozen"
f.frozen?
=> true
f.clone.frozen?
=> true
f.dup.frozen?
=> false