我认为ruby只是调用方法to_s但我无法解释它是如何工作的:
class Fake
def to_s
self
end
end
"#{Fake.new}"
根据逻辑,由于无穷大递归,这应该会使堆栈级别过高。但它工作正常,似乎从一个对象调用#to_s。
=> "#<Fake:0x137029f8>"
但为什么?
增加:
class Fake
def to_s
Fake2.new
end
end
class Fake2
def to_s
"Fake2#to_s"
end
end
此代码在两种情况下的工作方式不同:
puts "#{Fake.new}" => "#<Fake:0x137d5ac4>"
可是:
puts Fake.new.to_s => "Fake2#to_s"
我认为这是不正常的。有人可以建议在ruby解释器中它发生在内部吗?
答案 0 :(得分:11)
Ruby确实调用to_s
,但它会检查to_s
是否返回一个字符串。如果没有,ruby会调用to_s
的默认实现。以递归方式调用to_s
不是一个好主意(不保证终止) - 您可能会崩溃VM并且ruby代码不应该崩溃整个VM。
您获得Fake.new.to_s
的不同输出,因为irb调用inspect
向您显示结果,inspect
第二次调用to_s
要回答“当ruby执行x时会发生什么”,一个好的起点是查看为VM生成的指令(这是所有MRI特定的)。以你的例子:
puts RubyVM::InstructionSequence.compile('"#{Foo.new}"').disasm
输出
0000 trace 1 ( 1)
0002 getinlinecache 9, <is:0>
0005 getconstant :Foo
0007 setinlinecache <is:0>
0009 opt_send_simple <callinfo!mid:new, argc:0, ARGS_SKIP>
0011 tostring
0012 concatstrings 1
0014 leave
缓存存在一些问题,你总是得到trace
,leave
,但简而言之,这就是说。
此转储中的说明在insns.def中定义:这会将这些说明映射到其实现中。您可以看到tostring
只是致电rb_obj_as_string
。
如果您通过ruby代码库搜索rb_obj_as_string
(我发现http://rxr.whitequark.org对此有用),您可以将其定义为here
VALUE
rb_obj_as_string(VALUE obj)
{
VALUE str;
if (RB_TYPE_P(obj, T_STRING)) {
return obj;
}
str = rb_funcall(obj, id_to_s, 0);
if (!RB_TYPE_P(str, T_STRING))
return rb_any_to_s(obj);
if (OBJ_TAINTED(obj)) OBJ_TAINT(str);
return str;
}
简而言之,如果我们已经有一个字符串,那么返回它。如果没有,请调用对象的to_s
方法。然后,(这对你的问题至关重要),它会检查结果的类型。如果它不是字符串,则返回rb_any_to_s
,这是实现默认to_s
的函数