Ruby: #clone vs #dup
The Essence of Shallow Copy
According to Ruby-doc, both #clone and #dup can be used to create a shallow copy of an object, which only traverse one layer of complexity, meaning that the instance variables of obj are copied, but not the objects they reference. They would all share the same attributes; modifying one would result a change on another. However, #clone does two things different than #dup:
1) #clone maintains the frozen or tainted state of obj. whereas #dup would change it to tainted.
a = [1, 2, 3, 4, 5]
a.freezea_clone = a.clone
a_clone << 6 #=> can't modify frozen Arraya_dup = a.dup
a_dup << 6 #=> [1, 2, 3, 4, 5, 6]
In most Object-Oriented Programming languages, objects are generally mutable (or tainted) with a limited set of exceptions. In Ruby, mutability is a property of an instance, not of an entire class. Any instance can become immutable by calling #freeze. It turns an object into a constant and makes it easier to implement encapsulation. Therefore, if part of an object’s state is frozen, accessor methods can return that immutable object to outside callers without fear that those callers can change the object’s state.
class Fish
attr_accessor :name
@@all = [] def initialize(name)
@name = name
@@all << self
end def self.all
@@all.dup.freeze
end
endfish = Fish.all
fish << Human.new("John") #=> can't modify frozen Array
Ruby also automatically freezes the hash key if the key is a string. That way, if the original string changes later on, it would not affect the hash key.
fish = { “Dory” => { :name => “Dory”, :type => “Regal Blue Tang” },
“Nemo”=> { :name => “Nemo”, :type => “Clownfish” }
}fish.keys.first.frozen? #=> true
fish.keys.first.replace("Crazy Blue Fish") #=> can't modify frozen String
2) #clone copies any singleton methods of an object but #dup does not support this.
class Fish
attr_accessor :name
def initialize(name)
@name = name
end
enddory = Fish.new("Dory")def dory.whale_talk
"Mmmmoooooowaaaaah..."
enddory_clone = dory.clone
dory_clone.whale_talk #=> "Mmmmoooooowaaaaah..."dory_dup = dory.dup
dory_dup.whale_talk #=> undefined method `whale_talk' for #<Fish:0x0055f7b7917e90 @name="Dory">
In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance.
Essentially, #clone and #dup both produce shallow-copies of an object. #clone copies everything: internal state, singleton methods, etc. #dup copies object contents only (plus the tainted status).
Deep copy, on another hand, is used in scenarios where a new copy (clone) is created without any reference to original data (for more information on the difference between the two can be found on my another post)
*** Be Aware ***
In Rails 4.0, #clone and #dup behave slightly differently. #clone is the exact copy of an ActiveRecord object. They share all attributes, even the primary key.
Although #dup is still consider as a shallow copy, it does not have a direct association with the original; and thus, modifying the attributes of a duped object will not result a change in the original one.
[1] pry> original = User.find(1)
=> #<User id: 1, first_name: “John”, last_name: “Smith”, email: nil, created_at: “2016–08–07 15:17:03”, updated_at: “2016–08–07 16:01:43”>[2] pry> original.clone
=> #<User id: 1, first_name: “John”, last_name: “Smith”, email: nil, created_at: “2016–08–07 15:17:03”, updated_at: “2016–08–07 16:01:43”>[3] pry> original.dup
=> #<User id: nil, first_name: “John”, last_name: “Smith”, email: nil, created_at: nil, updated_at: nil>