157 views
What’s the return value of this Ruby 3 code?
obj = "x" def obj.tag = :singleton a = obj.dup b = obj.clone [a.respond_to?(:tag), b.respond_to?(:tag)] # => ???
The correct answer is
[true, false]
[false, true]
[true, true]
[false, false]
Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!
We begin with a simple string instance and attach a singleton method to it. Only that instance should have the method.
obj = "x"
This creates a fresh String
whose content is the character x. The object has its own identity and no custom behavior yet.
def obj.tag = :singleton
This opens the eigenclass of obj
and defines a method named tag
that returns the symbol :singleton
. The method belongs to obj
only. Other strings are not affected because String
itself has not changed.
obj.tag
This returns :singleton
. The call proves that the singleton method exists on this instance.
dup
Now we copy obj
with dup
and observe what was carried over.
a = obj.dup
This creates a shallow copy with the same class and the same content. The new object has a different identity. The eigenclass is not copied. The singleton method is not present on a
.
a.respond_to?(:tag)
This returns false
. The copy created by dup
does not have the tag
method.
a == obj
This returns true
. The two strings have equal content even though they are different objects.
a.equal?(obj)
This returns false
. equal?
checks identity. The objects are distinct.
clone
Now we copy obj
with clone
and compare the behavior.
b = obj.clone
This creates a shallow copy that preserves the eigenclass. The singleton method tag
is copied onto b
because the eigenclass relationship is preserved.
b.respond_to?(:tag)
This returns true
. The copy created by clone
responds to the singleton method.
b.tag
This returns :singleton
. The method behaves the same as on obj
.
It is useful to look at the list of singleton methods for each object.
obj.singleton_methods.sort
This returns [:tag]
. The original object exposes the singleton method.
a.singleton_methods.sort
This returns []
. The copy from dup
has no singleton methods.
b.singleton_methods.sort
This returns [:tag]
. The copy from clone
preserved the singleton method.
The simplest mental model is the following. dup
makes a content level copy of the object. It keeps instance variables and basic state. It does not carry over the meta level where singleton methods and per object module extensions live. clone
makes a closer copy. It preserves the meta level, the frozen state, and any taint or trust flags on older Rubies. For modern Ruby, you mostly care about singleton methods, extended modules, and frozen state. When in doubt, reach for dup
if you want a clean copy without ad hoc behavior. Reach for clone
if you need to keep exactly what the original could do.
We reproduce the minimal example and explain the outcome in one line per call.
obj = "x"
This creates the original string.
def obj.tag = :singleton
This adds a singleton method to the instance.
a = obj.dup
This makes a copy without the eigenclass. a
does not have tag
.
b = obj.clone
This makes a copy that preserves the eigenclass. b
has tag
.
[a.respond_to?(:tag), b.respond_to?(:tag)]
This produces [false, true]
. The first element is false
because dup
strips singleton behavior. The second element is true
because clone
preserves it.
Voilà!
Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!
RubyCademy ©