Which Ruby classes support .clone?

2019-02-12 18:51发布

Ruby defines #clone in Object. To my suprise, some classes raise Exceptions when calling it. I found NilClass, TrueClass, FalseClass, Fixnum having this behaviour.

1) Does a complete list of classes (at least core-classes) exist, which do not allow #clone ? Or is there a way to detect if a specific class supports #clone ?

2) What is wrong with 42.clone ?

标签: ruby clone
6条回答
兄弟一词,经得起流年.
2楼-- · 2019-02-12 19:29

I don't think there is a formal list, at least unless you count reading the source. The reason 2) doesn't work is because of an optimization applied to Fixnums. They are stored/passed internally as their actual values (so are true, false and nil), and not as pointers. The naive solution is to just have 42.clone return the same 42, but then the invariant obj.clone.object_id != obj.object_id would no longer hold, 42.clone wouldn't actually be cloning.

查看更多
Deceive 欺骗
3楼-- · 2019-02-12 19:30

Fixnum is a special class given special treatment by the language. From the time your program launches, there is precisely one Fixnum for every number that the class can represent, and they're given a special representation that doesn't take any extra space — this way, basic math operations aren't allocating and deallocating memory like crazy. Because of this, there cannot be more than one 42.

For the others, they all have one thing in common: They're singletons. There's only one instance of a singleton class by definition, so trying to clone it is an error.

查看更多
啃猪蹄的小仙女
4楼-- · 2019-02-12 19:32

I did a git grep "can't clone" of YARV's source code, and got

lib/singleton.rb:    raise TypeError, "can't clone instance of singleton #{self.class}"
object.c:        rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
test/test_singleton.rb:    expected = "can't clone instance of singleton TestSingleton::SingletonTest"

The first and third lines indicate you can't clone a singleton.

The second line refers to rb_special_const_p(obj). But this is going beyond my ken.

查看更多
做个烂人
5楼-- · 2019-02-12 19:37

Rails appears to extend the classes you mention with a "duplicable?()" method.

http://api.rubyonrails.org/files/activesupport/lib/active_support/core_ext/object/duplicable_rb.html

查看更多
冷血范
6楼-- · 2019-02-12 19:41

I still don't know how to test for clonability properly but here's a very clunky, evil way to test for clonablity using error trapping:

def clonable?(value)
  begin
    clone = value.clone
    true
  rescue
    false
  end
end

And here's how you can clone even the unclonable. At least for the very few classes I've tired it with.

def super_mega_clone(value)
  eval(value.inspect)
end

Here's some sample testing:

b = :b
puts "clonable? #{clonable? b}"

b = proc { b == "b" }
puts "clonable? #{clonable? b}"

b = [:a, :b, :c]
c = super_mega_clone(b)

puts "c: #{c.object_id}"
puts "b: #{b.object_id}"
puts "b == c => #{b == c}"
b.each_with_index do |value, index|
  puts "[#{index}] b: #{b[index].object_id} c: #{c[index].object_id}"
end
b[0] = :z

puts "b == c => #{b == c}"
b.each_with_index do |value, index|
  puts "[#{index}] b: #{b[index].object_id} c: #{c[index].object_id}"
end

b = :a
c = super_mega_clone(b)
puts "b: #{b.object_id} c: #{c.object_id}"

> clonable? false
> clonable? true
> c: 2153757040
> b: 2153757480
> b == c => true
> [0] b: 255528 c: 255528
> [1] b: 255688 c: 255688
> [2] b: 374568 c: 374568
> b == c => false
> [0] b: 1023528 c: 255528
> [1] b: 255688 c: 255688
> [2] b: 374568 c: 374568
> b: 255528 c: 255528
查看更多
再贱就再见
7楼-- · 2019-02-12 19:48

You can't clone immutable classes. I.e. you can have only one instance of object 42 (as a Fixnum), but can have many instances of "42" (because string is mutable). You can't clone symbols as well since they are something like immutable strings.

You can check that in IRB with object_id method. (symbols and fixnums will give you same object_id after repetitive calls)

查看更多
登录 后发表回答