¿Cuál es la diferencia entre los métodos dup y clone de Ruby?

Los documentos de Ruby para dup dicen:

En general, clone y dup pueden tener una semántica diferente en las clases descendientes. Mientras que el clone se usa para duplicar un objeto, incluido su estado interno, dup normalmente usa la clase del objeto descendiente para crear la nueva instancia.

Pero cuando hago una prueba, descubro que en realidad son lo mismo:

 class Test attr_accessor :x end x = Test.new xx = 7 y = x.dup z = x.clone yx => 7 zx => 7 

Entonces, ¿cuáles son las diferencias entre los dos métodos?

Las subclases pueden anular estos métodos para proporcionar semántica diferente. En Object sí, hay dos diferencias clave.

En primer lugar, clone copia la clase singleton, mientras que dup no lo hace.

 o = Object.new def o.foo 42 end o.dup.foo # raises NoMethodError o.clone.foo # returns 42 

En segundo lugar, el clone conserva el estado congelado, mientras que el dup no lo congela.

 class Foo attr_accessor :bar end o = Foo.new o.freeze o.dup.bar = 10 # succeeds o.clone.bar = 10 # raises RuntimeError 

La implementación de Rubinius para estos métodos suele ser mi fuente de respuestas a estas preguntas, ya que es bastante clara, y una implementación de Ruby bastante compatible.

Cuando se trata de ActiveRecord también hay una diferencia significativa:

dup crea un nuevo objeto sin que se establezca su id, por lo que puede guardar un nuevo objeto en la base de datos pulsando .save

 category2 = category.dup #=> # 

clone crea un nuevo objeto con el mismo ID, por lo que todos los cambios realizados en ese nuevo objeto sobrescribirán el registro original si se golpea .save

 category2 = category.clone #=> # 

Una diferencia es con objetos congelados. El clone de un objeto congelado también está congelado (mientras que un dup de un objeto congelado no lo está).

 class Test attr_accessor :x end x = Test.new xx = 7 x.freeze y = x.dup z = x.clone yx = 5 => 5 zx = 5 => TypeError: can't modify frozen object 

Otra diferencia es con los métodos singleton. La misma historia aquí, dup no copia esos, pero clone hace.

 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! 

El documento más nuevo incluye un buen ejemplo:

 class Klass attr_accessor :str end module Foo def foo; 'foo'; end end s1 = Klass.new #=> # s1.extend(Foo) #=> # s1.foo #=> "foo" s2 = s1.clone #=> # s2.foo #=> "foo" s3 = s1.dup #=> # s3.foo #=> NoMethodError: undefined method `foo' for # 

Ambos son casi idénticos, pero clon hace una cosa más que dup. En clon, el estado congelado del objeto también se copia. En dup, siempre se descongelará.

  f = 'Frozen'.freeze => "Frozen" f.frozen? => true f.clone.frozen? => true f.dup.frozen? => false