Ruby: #clone vs #dup

Raycent Tan
3 min readAug 8, 2016

--

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.freeze
a_clone = a.clone
a_clone << 6 #=> can't modify frozen Array
a_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
end
fish = 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
end
dory = Fish.new("Dory")def dory.whale_talk
"Mmmmoooooowaaaaah..."
end
dory_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.

Identical to Ruby’s clone method. This is a “shallow” copy. Be warned that your attributes are not copied. That means that modifying attributes of the clone will modify the original, since they will both point to the same attributes hash. If you need a copy of your attributes hash, please use the #dup method.

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.

Duped objects have no id assigned and are treated as new records. Note that this is a “shallow” copy as it copies the object’s attributes only, not its associations. The extent of a “deep” copy is application specific and is therefore left to the application to implement according to its need.

[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>

--

--

No responses yet