Ruby

What is the difference between clone and dup in Ruby

When you want to protect objects from being changed inside the methods you send them, Ruby offers some methods to preserve that original objects will not get altered in method calls. These methods are: dup and clone which are very similar with two differences.

To demonstrate, a classic mutable hello_world string will be used and a method alterer() that reverses the supplied string. To avoid repetition in the code gists, a file alrerer.rb will contain the method definition and it will be required in every case. The method will reverse the string using the method reverse!, so the alterer.rb file will contain the following code:

def alterer(str)
  str.reverse!
end

Inspect the method behaviour with a normal object

The first experiment to perform is supplying the hello_world string to the alterer and inspect the original string object for any changes. Let's run the code below:

require "./alterer.rb"
hello_world = "Hello world"
alterer(hello_world)
puts hello_world

By running the above gist, the hello_world string changes inside the method alterer() giving the following output:

dlrow olleH

The method dup

If you wanted the string to remain unchanged inside the method, you should use the dup method, which duplicates the object leaving the original object untouched. Therefore, the previous gist should look like the following:

require "./alterer.rb"
hello_world = "Hello world"
alterer(hello_world.dup)
puts hello_world

You can easily inspect that the output is the original string unchanged!

Hello world

The freeze method

Another way of preventing the change of a mutable object inside a method is by freezing it, with the freeze method. We can use the method freeze before passing the hello_world string to the alterer and get the following:

require "./alterer.rb"
hello_world = "Hello world"
hello_world.freeze
alterer(hello_world)
puts hello_world

In the above gist the method alterer gets a frozen object and tries to reverse it, as a result it throws a RuntimeError:

alterer.rb:2:in `reverse!': can't modify frozen String (RuntimeError)

In general, any frozen object can not get modified and there is no corresponding unfreeze method. Once you use the freeze method in an object, the object remains frozen for the rest of its lifecycle, that’s it!

The clone method and the difference with dup method regarding frozen objects

Additionally, there is a similar method to dup, the clone method. The difference between them (regarding frozen objects) is that if you use the method dup on a frozen object, the duplicate is not frozen, whereas when you use the clone method on a frozen object the cloned object is also frozen!

require "./alterer.rb"
hello_world = "Hello world"
hello_world.freeze
alterer(hello_world.dup)
puts hello_world

which outputs the following:

Hello world

cloning a frozen object

require "./alterer.rb"
hello_world = "Hello world"
hello_world.freeze
alterer(hello_world.clone)
puts hello_world

which throws a RuntimeError:

alterer.rb:2:in `reverse!': can't modify frozen String (RuntimeError)

So keep in mind that duplicating a frozen object gives a NOT frozen, cloning a frozen object gives a frozen!

Preservation of singleton methods

Additionally to the difference with the frozen objects, there is one more regarding the singleton methods of the duped/cloned objects. To demonstrate this, a newly created object obj and a singleton method m will be used:

obj = Object.new
def obj.m
  puts "I am a singleton method"
end

With the above gist in mind, let’s inspect the difference by checking the response of a cloned and the duped object to the singleton method m:

>> obj.clone.respond_to?(:m)
=> true

>> obj.dup.respond_to?(:m)
=> false

It is clear that, the cloned object preserves all the singleton methods whereas a duped object does not. Having seen there two different behaviors (regarding frozen objects and singleton methods) we can lead to the assumption that clone provides a deeper copy of a particular object than dup.

Buy Me A Coffee

Read also the following